mirror of
https://github.com/h7x4/Jisho-Study-Tool.git
synced 2024-12-21 13:37:29 +01:00
Add lots of history functionality
This commit is contained in:
parent
d82fcbe427
commit
e8f42860af
@ -1,7 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
|
||||
import 'package:jisho_study_tool/models/history/kanji_result.dart';
|
||||
import 'package:jisho_study_tool/models/history/kanji_query.dart';
|
||||
import 'package:jisho_study_tool/models/history/search.dart';
|
||||
|
||||
import './kanji_event.dart';
|
||||
import './kanji_state.dart';
|
||||
@ -14,7 +15,6 @@ export './kanji_event.dart';
|
||||
export './kanji_state.dart';
|
||||
|
||||
class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
|
||||
|
||||
DatabaseBloc _databaseBloc;
|
||||
|
||||
KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial));
|
||||
@ -24,34 +24,32 @@ class KanjiBloc extends Bloc<KanjiEvent, KanjiState> {
|
||||
throw DatabaseNotConnectedException;
|
||||
|
||||
(_databaseBloc.state as DatabaseConnected)
|
||||
.database
|
||||
.box<KanjiResult>()
|
||||
.put(KanjiResult(
|
||||
kanji: kanji,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
.database
|
||||
.box<Search>()
|
||||
.put(Search(timestamp: DateTime.now())
|
||||
..kanjiQuery.target = KanjiQuery(
|
||||
kanji: kanji,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<KanjiState> mapEventToState(KanjiEvent event)
|
||||
async* {
|
||||
Stream<KanjiState> mapEventToState(KanjiEvent event) async* {
|
||||
if (event is GetKanji) {
|
||||
|
||||
yield KanjiSearchLoading();
|
||||
|
||||
try {
|
||||
addSearchToDB(event.kanjiSearchString);
|
||||
final kanji = await fetchKanji(event.kanjiSearchString);
|
||||
if (kanji.found) yield KanjiSearchFinished(kanji: kanji);
|
||||
else yield KanjiSearchError('Something went wrong');
|
||||
if (kanji.found)
|
||||
yield KanjiSearchFinished(kanji: kanji);
|
||||
else
|
||||
yield KanjiSearchError('Something went wrong');
|
||||
} on Exception {
|
||||
yield KanjiSearchError('Something went wrong');
|
||||
}
|
||||
} else if (event is GetKanjiSuggestions) {
|
||||
|
||||
final suggestions = kanjiSuggestions(event.searchString);
|
||||
yield KanjiSearchKeyboard(KanjiSearchType.Keyboard, suggestions);
|
||||
|
||||
} else if (event is ReturnToInitialState) {
|
||||
yield KanjiSearch(KanjiSearchType.Initial);
|
||||
}
|
||||
|
18
lib/bloc/navigation/navigation_bloc.dart
Normal file
18
lib/bloc/navigation/navigation_bloc.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import './navigation_event.dart';
|
||||
import './navigation_state.dart';
|
||||
|
||||
export './navigation_event.dart';
|
||||
export './navigation_state.dart';
|
||||
|
||||
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
|
||||
|
||||
NavigationBloc() : super(NavigationPage(0));
|
||||
|
||||
@override
|
||||
Stream<NavigationState> mapEventToState(NavigationEvent event) async* {
|
||||
if (event is ChangePage)
|
||||
yield NavigationPage(event.pageNum);
|
||||
}
|
||||
}
|
8
lib/bloc/navigation/navigation_event.dart
Normal file
8
lib/bloc/navigation/navigation_event.dart
Normal file
@ -0,0 +1,8 @@
|
||||
abstract class NavigationEvent {
|
||||
const NavigationEvent();
|
||||
}
|
||||
|
||||
class ChangePage extends NavigationEvent {
|
||||
final int pageNum;
|
||||
const ChangePage(this.pageNum);
|
||||
}
|
9
lib/bloc/navigation/navigation_state.dart
Normal file
9
lib/bloc/navigation/navigation_state.dart
Normal file
@ -0,0 +1,9 @@
|
||||
abstract class NavigationState {
|
||||
const NavigationState();
|
||||
}
|
||||
|
||||
class NavigationPage extends NavigationState {
|
||||
final int pageNum;
|
||||
const NavigationPage(this.pageNum);
|
||||
|
||||
}
|
@ -3,7 +3,8 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jisho_study_tool/models/history/search_string.dart';
|
||||
import 'package:jisho_study_tool/models/history/search.dart';
|
||||
import 'package:jisho_study_tool/models/history/word_query.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
|
||||
@ -14,7 +15,6 @@ part 'search_event.dart';
|
||||
part 'search_state.dart';
|
||||
|
||||
class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
||||
|
||||
DatabaseBloc _databaseBloc;
|
||||
|
||||
SearchBloc(this._databaseBloc) : super(SearchInitial());
|
||||
@ -24,12 +24,12 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
||||
throw DatabaseNotConnectedException;
|
||||
|
||||
(_databaseBloc.state as DatabaseConnected)
|
||||
.database
|
||||
.box<SearchString>()
|
||||
.put(SearchString(
|
||||
query: searchString,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
.database
|
||||
.box<Search>()
|
||||
.put(Search(timestamp: DateTime.now())
|
||||
..wordQuery.target = WordQuery(
|
||||
query: searchString,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -42,7 +42,8 @@ class SearchBloc extends Bloc<SearchEvent, SearchState> {
|
||||
try {
|
||||
addSearchToDB(event.searchString);
|
||||
final searchResults = await fetchJishoResults(event.searchString);
|
||||
if (searchResults.meta.status == 200) yield SearchFinished(searchResults.data!);
|
||||
if (searchResults.meta.status == 200)
|
||||
yield SearchFinished(searchResults.data!);
|
||||
} on Exception {
|
||||
yield SearchError('Something went wrong');
|
||||
}
|
||||
|
149
lib/main.dart
149
lib/main.dart
@ -1,13 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:jisho_study_tool/view/screens/loading.dart';
|
||||
import 'package:mdi/mdi.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import 'package:jisho_study_tool/objectbox.g.dart';
|
||||
|
||||
import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
|
||||
|
||||
import 'package:jisho_study_tool/view/screens/kanji/view.dart';
|
||||
import 'package:jisho_study_tool/view/screens/history.dart';
|
||||
@ -18,49 +21,26 @@ void main() => runApp(MyApp());
|
||||
|
||||
DatabaseBloc _databaseBloc = DatabaseBloc();
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
class MyApp extends StatefulWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Jisho Study Tool',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (context) => SearchBloc(_databaseBloc)),
|
||||
BlocProvider(create: (context) => KanjiBloc(_databaseBloc)),
|
||||
BlocProvider(create: (context) => _databaseBloc),
|
||||
],
|
||||
child: Home(),
|
||||
),
|
||||
);
|
||||
}
|
||||
_MyAppState createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
@override
|
||||
_HomeState createState() => _HomeState();
|
||||
}
|
||||
|
||||
class _HomeState extends State<Home> {
|
||||
int selectedPage = 0;
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
late final Store _store;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getApplicationDocumentsDirectory()
|
||||
.then((dir) {
|
||||
_store = Store(
|
||||
getObjectBoxModel(),
|
||||
directory: join(dir.path, 'objectbox'),
|
||||
);
|
||||
getApplicationDocumentsDirectory().then((dir) {
|
||||
_store = Store(
|
||||
getObjectBoxModel(),
|
||||
directory: join(dir.path, 'objectbox'),
|
||||
);
|
||||
|
||||
_databaseBloc.add(ConnectedToDatabase(_store));
|
||||
});
|
||||
_databaseBloc.add(ConnectedToDatabase(_store));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -72,14 +52,41 @@ class _HomeState extends State<Home> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DatabaseBloc, DatabaseState>(
|
||||
builder: (context, state) {
|
||||
return MaterialApp(
|
||||
title: 'Jisho Study Tool',
|
||||
|
||||
if (state is DatabaseDisconnected) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
// TODO: Add color theme
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (context) => SearchBloc(_databaseBloc)),
|
||||
BlocProvider(create: (context) => KanjiBloc(_databaseBloc)),
|
||||
BlocProvider(create: (context) => _databaseBloc),
|
||||
BlocProvider(create: (context) => NavigationBloc()),
|
||||
],
|
||||
child:
|
||||
BlocBuilder<DatabaseBloc, DatabaseState>(builder: (context, state) {
|
||||
if (state is DatabaseDisconnected)
|
||||
return Container(
|
||||
child: LoadingScreen(),
|
||||
decoration: BoxDecoration(color: Colors.white),
|
||||
);
|
||||
|
||||
return Home();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NavigationBloc, NavigationState>(
|
||||
builder: (context, state) {
|
||||
int selectedPage = (state as NavigationPage).pageNum;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@ -89,12 +96,9 @@ class _HomeState extends State<Home> {
|
||||
body: pages[selectedPage].content,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: selectedPage,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
selectedPage = index;
|
||||
});
|
||||
},
|
||||
items: navBar,
|
||||
onTap: (int index) =>
|
||||
BlocProvider.of<NavigationBloc>(context).add(ChangePage(index)),
|
||||
items: pages.map((p) => p.item).toList(),
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
unselectedItemColor: Colors.blue,
|
||||
@ -106,38 +110,15 @@ class _HomeState extends State<Home> {
|
||||
}
|
||||
}
|
||||
|
||||
final List<BottomNavigationBarItem> navBar = [
|
||||
BottomNavigationBarItem(
|
||||
label: 'Search',
|
||||
icon: Icon(Icons.search),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'Kanji',
|
||||
icon: Icon(
|
||||
Mdi.ideogramCjk,
|
||||
size: 30,
|
||||
)),
|
||||
BottomNavigationBarItem(
|
||||
label: 'History',
|
||||
icon: Icon(Icons.history),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'Memorize',
|
||||
icon: Icon(Icons.bookmark),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'Settings',
|
||||
icon: Icon(Icons.settings),
|
||||
),
|
||||
];
|
||||
|
||||
class _Page {
|
||||
Widget content;
|
||||
Widget titleBar;
|
||||
final Widget content;
|
||||
final Widget titleBar;
|
||||
final BottomNavigationBarItem item;
|
||||
|
||||
_Page({
|
||||
const _Page({
|
||||
required this.content,
|
||||
required this.titleBar,
|
||||
required this.item,
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,21 +126,39 @@ final List<_Page> pages = [
|
||||
_Page(
|
||||
content: SearchView(),
|
||||
titleBar: Text('Search'),
|
||||
item: BottomNavigationBarItem(
|
||||
label: 'Search',
|
||||
icon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
_Page(
|
||||
content: KanjiView(),
|
||||
titleBar: KanjiViewBar(),
|
||||
item: BottomNavigationBarItem(
|
||||
label: 'Kanji', icon: Icon(Mdi.ideogramCjk, size: 30)),
|
||||
),
|
||||
_Page(
|
||||
content: HistoryView(),
|
||||
titleBar: Text("History"),
|
||||
item: BottomNavigationBarItem(
|
||||
label: 'History',
|
||||
icon: Icon(Icons.history),
|
||||
),
|
||||
),
|
||||
_Page(
|
||||
content: Container(),
|
||||
titleBar: Text("Memorization"),
|
||||
titleBar: Text("Saved"),
|
||||
item: BottomNavigationBarItem(
|
||||
label: 'Saved',
|
||||
icon: Icon(Icons.bookmark),
|
||||
),
|
||||
),
|
||||
_Page(
|
||||
content: Container(),
|
||||
titleBar: Text("Settings"),
|
||||
item: BottomNavigationBarItem(
|
||||
label: 'Settings',
|
||||
icon: Icon(Icons.settings),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
13
lib/models/history/kanji_query.dart
Normal file
13
lib/models/history/kanji_query.dart
Normal file
@ -0,0 +1,13 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
@Entity()
|
||||
class KanjiQuery {
|
||||
int id;
|
||||
|
||||
String kanji;
|
||||
|
||||
KanjiQuery({
|
||||
this.id = 0,
|
||||
required this.kanji,
|
||||
});
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
@Entity()
|
||||
class KanjiResult {
|
||||
int id = 0;
|
||||
|
||||
@Property(type: PropertyType.date)
|
||||
DateTime timestamp;
|
||||
|
||||
String kanji;
|
||||
|
||||
KanjiResult({
|
||||
required this.timestamp,
|
||||
required this.kanji,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "[${timestamp.toIso8601String()}] - $kanji";
|
||||
}
|
||||
}
|
30
lib/models/history/search.dart
Normal file
30
lib/models/history/search.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
import './kanji_query.dart';
|
||||
import './word_query.dart';
|
||||
|
||||
@Entity()
|
||||
class Search {
|
||||
int id;
|
||||
|
||||
@Property(type: PropertyType.date)
|
||||
late final DateTime timestamp;
|
||||
|
||||
final wordQuery = ToOne<WordQuery>();
|
||||
|
||||
final kanjiQuery = ToOne<KanjiQuery>();
|
||||
|
||||
Search({
|
||||
this.id = 0,
|
||||
required this.timestamp
|
||||
}); // {
|
||||
|
||||
bool isKanji() {
|
||||
// // TODO: better error message
|
||||
if (this.wordQuery.target == null && this.kanjiQuery.target == null)
|
||||
throw Exception();
|
||||
|
||||
return this.wordQuery.target == null;
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
import 'package:jisho_study_tool/models/history/word_result.dart';
|
||||
|
||||
@Entity()
|
||||
class SearchString {
|
||||
int id = 0;
|
||||
|
||||
@Property(type: PropertyType.date)
|
||||
DateTime timestamp;
|
||||
|
||||
String query;
|
||||
|
||||
@Backlink()
|
||||
final chosenResults = ToMany<WordResult>();
|
||||
|
||||
SearchString({
|
||||
required this.timestamp,
|
||||
required this.query,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "[${timestamp.toIso8601String()}] \"$query\"";
|
||||
}
|
||||
}
|
19
lib/models/history/word_query.dart
Normal file
19
lib/models/history/word_query.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
import './word_result.dart';
|
||||
|
||||
@Entity()
|
||||
class WordQuery {
|
||||
int id;
|
||||
|
||||
String query;
|
||||
|
||||
// TODO: Link query with results that the user clicks onto.
|
||||
@Backlink()
|
||||
final chosenResults = ToMany<WordResult>();
|
||||
|
||||
WordQuery({
|
||||
this.id = 0,
|
||||
required this.query,
|
||||
});
|
||||
}
|
@ -1,25 +1,21 @@
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
import 'package:jisho_study_tool/models/history/search_string.dart';
|
||||
import 'package:jisho_study_tool/models/history/word_query.dart';
|
||||
|
||||
@Entity()
|
||||
class WordResult {
|
||||
int id = 0;
|
||||
int id;
|
||||
|
||||
@Property(type: PropertyType.date)
|
||||
DateTime timestamp;
|
||||
|
||||
String word;
|
||||
|
||||
final searchString = ToOne<SearchString>();
|
||||
final searchString = ToOne<WordQuery>();
|
||||
|
||||
WordResult({
|
||||
this.id = 0,
|
||||
required this.timestamp,
|
||||
required this.word,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "[${timestamp.toIso8601String()}] - $word";
|
||||
}
|
||||
}
|
@ -3,54 +3,6 @@
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:8135239166970424087",
|
||||
"lastPropertyId": "3:1930470268740402049",
|
||||
"name": "KanjiResult",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:2681934095975267680",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:4514526257378540330",
|
||||
"name": "timestamp",
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"id": "3:1930470268740402049",
|
||||
"name": "kanji",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "2:461492167249325765",
|
||||
"lastPropertyId": "3:7573103520245228403",
|
||||
"name": "SearchString",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:4297905889790758495",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:4157902147911002923",
|
||||
"name": "timestamp",
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"id": "3:7573103520245228403",
|
||||
"name": "query",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "3:8314315977756262774",
|
||||
"lastPropertyId": "4:7972948456299367594",
|
||||
@ -78,21 +30,112 @@
|
||||
"type": 11,
|
||||
"flags": 520,
|
||||
"indexId": "1:6146948198859733323",
|
||||
"relationTarget": "SearchString"
|
||||
"relationTarget": "WordQuery"
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "4:4256390943850643278",
|
||||
"lastPropertyId": "3:1496429060084558178",
|
||||
"name": "KanjiQuery",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:2966275213904862677",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:3733952844232949036",
|
||||
"name": "kanji",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "5:3499538826755540666",
|
||||
"lastPropertyId": "3:1154921921492752045",
|
||||
"name": "WordQuery",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:2582448470002735577",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:6622038022626247037",
|
||||
"name": "query",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "6:8118874861016646859",
|
||||
"lastPropertyId": "5:818915488505962903",
|
||||
"name": "Search",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:3233720904924970047",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:7793044338609887616",
|
||||
"name": "timestamp",
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"id": "4:5737790291742758071",
|
||||
"name": "wordQueryId",
|
||||
"type": 11,
|
||||
"flags": 520,
|
||||
"indexId": "4:4174896839978600983",
|
||||
"relationTarget": "WordQuery"
|
||||
},
|
||||
{
|
||||
"id": "5:818915488505962903",
|
||||
"name": "kanjiQueryId",
|
||||
"type": 11,
|
||||
"flags": 520,
|
||||
"indexId": "5:5394995618034342416",
|
||||
"relationTarget": "KanjiQuery"
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "3:8314315977756262774",
|
||||
"lastIndexId": "1:6146948198859733323",
|
||||
"lastRelationId": "0:0",
|
||||
"lastEntityId": "6:8118874861016646859",
|
||||
"lastIndexId": "5:5394995618034342416",
|
||||
"lastRelationId": "1:2624712325077938293",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [],
|
||||
"retiredRelationUids": [],
|
||||
"retiredEntityUids": [
|
||||
8135239166970424087,
|
||||
461492167249325765
|
||||
],
|
||||
"retiredIndexUids": [
|
||||
2344626140411525437,
|
||||
1957456749938325194
|
||||
],
|
||||
"retiredPropertyUids": [
|
||||
2681934095975267680,
|
||||
4514526257378540330,
|
||||
1930470268740402049,
|
||||
4297905889790758495,
|
||||
4157902147911002923,
|
||||
7573103520245228403,
|
||||
1496429060084558178,
|
||||
1154921921492752045,
|
||||
2254834401134912797
|
||||
],
|
||||
"retiredRelationUids": [
|
||||
2624712325077938293
|
||||
],
|
||||
"version": 1
|
||||
}
|
51
lib/view/components/history/date_divider.dart
Normal file
51
lib/view/components/history/date_divider.dart
Normal file
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DateDivider extends StatelessWidget {
|
||||
final String? text;
|
||||
final DateTime? date;
|
||||
|
||||
const DateDivider({this.text, this.date, Key? key}) : super(key: key);
|
||||
|
||||
String getHumanReadableDate(DateTime date) {
|
||||
const Map<int, String> monthTable = {
|
||||
1: 'Jan',
|
||||
2: 'Feb',
|
||||
3: 'Mar',
|
||||
4: 'Apr',
|
||||
5: 'May',
|
||||
6: 'Jun',
|
||||
7: 'Jul',
|
||||
8: 'Aug',
|
||||
9: 'Sep',
|
||||
10: 'Oct',
|
||||
11: 'Nov',
|
||||
12: 'Dec',
|
||||
};
|
||||
|
||||
int day = date.day;
|
||||
String month = monthTable[date.month]!;
|
||||
int year = date.year;
|
||||
return "$day. $month $year";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget header = (this.text != null)
|
||||
? Text(this.text!)
|
||||
: (this.date != null)
|
||||
? Text(getHumanReadableDate(this.date!))
|
||||
: SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
child: DefaultTextStyle.merge(
|
||||
child: header,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
decoration: BoxDecoration(color: Colors.grey),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
horizontal: 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,39 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:jisho_study_tool/models/history/kanji_result.dart';
|
||||
import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
|
||||
import 'package:jisho_study_tool/models/history/kanji_query.dart';
|
||||
|
||||
class _KanjiSearchItemHeader extends StatelessWidget {
|
||||
final KanjiResult result;
|
||||
import './search_item.dart';
|
||||
|
||||
const _KanjiSearchItemHeader(this.result, {Key? key}) : super(key: key);
|
||||
class _KanjiBox extends StatelessWidget {
|
||||
final String kanji;
|
||||
|
||||
const _KanjiBox(this.kanji);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text("[KANJI] ${result.kanji} - ${result.timestamp.toIso8601String()}");
|
||||
return IntrinsicHeight(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
kanji,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 25,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KanjiSearchItem extends StatelessWidget {
|
||||
final KanjiResult result;
|
||||
final KanjiQuery result;
|
||||
final DateTime timestamp;
|
||||
|
||||
const KanjiSearchItem(this.result,{Key? key}) : super(key: key);
|
||||
const KanjiSearchItem({
|
||||
required this.result,
|
||||
required this.timestamp,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Slidable(
|
||||
child: ListTile(title: _KanjiSearchItemHeader(result)),
|
||||
child: SearchItem(
|
||||
onTap: () {
|
||||
BlocProvider.of<NavigationBloc>(context).add(ChangePage(1));
|
||||
BlocProvider.of<KanjiBloc>(context).add(GetKanji(this.result.kanji));
|
||||
},
|
||||
time: timestamp,
|
||||
search: _KanjiBox(result.kanji),
|
||||
),
|
||||
actionPane: SlidableScrollActionPane(),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
caption: "Favourite",
|
||||
color: Colors.yellow,
|
||||
icon: Icons.star
|
||||
icon: Icons.star,
|
||||
),
|
||||
IconSlideAction(
|
||||
caption: "Delete",
|
||||
color: Colors.red,
|
||||
icon: Icons.delete
|
||||
)
|
||||
icon: Icons.delete,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
38
lib/view/components/history/phrase_search_item.dart
Normal file
38
lib/view/components/history/phrase_search_item.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:jisho_study_tool/bloc/navigation/navigation_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/search/search_bloc.dart';
|
||||
import 'package:jisho_study_tool/models/history/word_query.dart';
|
||||
|
||||
import './search_item.dart';
|
||||
|
||||
class PhraseSearchItem extends StatelessWidget {
|
||||
final WordQuery search;
|
||||
final DateTime timestamp;
|
||||
|
||||
const PhraseSearchItem({
|
||||
required this.search,
|
||||
required this.timestamp,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Slidable(
|
||||
actionPane: SlidableScrollActionPane(),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
caption: "Delete", color: Colors.red, icon: Icons.delete)
|
||||
],
|
||||
child: SearchItem(
|
||||
onTap: () {
|
||||
BlocProvider.of<NavigationBloc>(context).add(ChangePage(0));
|
||||
BlocProvider.of<SearchBloc>(context).add(GetSearchResults(this.search.query));
|
||||
},
|
||||
time: timestamp,
|
||||
search: Text(search.query),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,41 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:jisho_study_tool/models/history/search_string.dart';
|
||||
|
||||
class SearchItemHeader extends StatelessWidget {
|
||||
final SearchString _search;
|
||||
class SearchItem extends StatelessWidget {
|
||||
final DateTime time;
|
||||
final Widget search;
|
||||
final void Function()? onTap;
|
||||
|
||||
const SearchItemHeader(this._search, {Key? key}) : super(key: key);
|
||||
const SearchItem({
|
||||
required this.time,
|
||||
required this.search,
|
||||
this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
String getTime() {
|
||||
final hours = this.time.hour.toString().padLeft(2, '0');
|
||||
final mins = this.time.minute.toString().padLeft(2, '0');
|
||||
return "$hours:$mins";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Text("[SEARCH] ${_search.query} - ${_search.timestamp.toString()}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchItem extends StatelessWidget {
|
||||
final SearchString _search;
|
||||
|
||||
const SearchItem(this._search, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Slidable(
|
||||
actionPane: SlidableScrollActionPane(),
|
||||
secondaryActions: [
|
||||
IconSlideAction(
|
||||
caption: "Delete",
|
||||
color: Colors.red,
|
||||
icon: Icons.delete
|
||||
)
|
||||
],
|
||||
child: ExpansionTile(
|
||||
title: SearchItemHeader(_search),
|
||||
expandedAlignment: Alignment.topCenter,
|
||||
children: [ListTile(title: Text(_search.timestamp.toIso8601String()),)],
|
||||
)
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(getTime()),
|
||||
),
|
||||
search,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,12 @@ class KanjiSearchBar extends StatefulWidget {
|
||||
enum TextFieldButton {clear, paste}
|
||||
|
||||
class _KanjiSearchBarState extends State<KanjiSearchBar> {
|
||||
FocusNode focus = new FocusNode();
|
||||
TextEditingController textController = new TextEditingController();
|
||||
final TextEditingController textController = new TextEditingController();
|
||||
TextFieldButton button = TextFieldButton.paste;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// focus.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
void _getKanjiSuggestions(String text) =>
|
||||
@ -57,10 +55,8 @@ class _KanjiSearchBarState extends State<KanjiSearchBar> {
|
||||
return TextField(
|
||||
controller: textController,
|
||||
onChanged: (text) => _getKanjiSuggestions(text),
|
||||
onSubmitted: (text) => {},
|
||||
// BlocProvider.of<KanjiBloc>(context).add(GetKanji(text)),
|
||||
onSubmitted: (_) => {},
|
||||
decoration: new InputDecoration(
|
||||
|
||||
prefixIcon: Icon(Icons.search),
|
||||
hintText: 'Search',
|
||||
fillColor: Colors.white,
|
||||
|
@ -1,35 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:jisho_study_tool/bloc/database/database_bloc.dart';
|
||||
import 'package:jisho_study_tool/models/history/kanji_result.dart';
|
||||
import 'package:jisho_study_tool/models/history/search_string.dart';
|
||||
import 'package:jisho_study_tool/models/history/search.dart';
|
||||
import 'package:jisho_study_tool/view/components/history/kanji_search_item.dart';
|
||||
import 'package:jisho_study_tool/view/components/history/search_item.dart';
|
||||
import 'package:jisho_study_tool/view/components/history/phrase_search_item.dart';
|
||||
import 'package:jisho_study_tool/view/components/history/date_divider.dart';
|
||||
|
||||
import 'package:jisho_study_tool/objectbox.g.dart';
|
||||
|
||||
class HistoryView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// return ListView.builder(
|
||||
// itemBuilder: (context, index) => ListTile(),
|
||||
// );
|
||||
return BlocBuilder<DatabaseBloc, DatabaseState>(
|
||||
builder: (context, state) {
|
||||
if (state is DatabaseDisconnected)
|
||||
throw DatabaseNotConnectedException();
|
||||
return ListView(
|
||||
children: (state as DatabaseConnected)
|
||||
.database
|
||||
.box<SearchString>()
|
||||
.getAll()
|
||||
.map((e) => SearchItem(e) as Widget)
|
||||
.toList()
|
||||
|
||||
+ (state as DatabaseConnected)
|
||||
.database
|
||||
.box<KanjiResult>()
|
||||
.getAll()
|
||||
.map((e) => KanjiSearchItem(e) as Widget)
|
||||
.toList(),
|
||||
return StreamBuilder(
|
||||
stream: ((state as DatabaseConnected).database.box<Search>().query()
|
||||
..order(Search_.timestamp, flags: Order.descending))
|
||||
.watch(triggerImmediately: true)
|
||||
.map((query) => query.find()),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (!snapshot.hasData) return Container();
|
||||
return ListView.separated(
|
||||
itemCount: snapshot.data.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) return Container();
|
||||
Search search = snapshot.data[index - 1];
|
||||
if (search.isKanji()) {
|
||||
return KanjiSearchItem(
|
||||
result: search.kanjiQuery.target!,
|
||||
timestamp: search.timestamp,
|
||||
);
|
||||
}
|
||||
return PhraseSearchItem(
|
||||
search: search.wordQuery.target!,
|
||||
timestamp: search.timestamp,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
Function roundToDay = (DateTime date) =>
|
||||
DateTime(date.year, date.month, date.day);
|
||||
|
||||
Search search = snapshot.data[index];
|
||||
DateTime searchDate = roundToDay(search.timestamp);
|
||||
|
||||
bool newDate = true;
|
||||
|
||||
if (index != 0) {
|
||||
Search prevSearch = snapshot.data[index - 1];
|
||||
|
||||
DateTime prevSearchDate = roundToDay(prevSearch.timestamp);
|
||||
newDate = prevSearchDate != searchDate;
|
||||
}
|
||||
|
||||
if (newDate) {
|
||||
if (searchDate == roundToDay(DateTime.now()))
|
||||
return DateDivider(text: "Today");
|
||||
else if (searchDate ==
|
||||
roundToDay(
|
||||
DateTime.now().subtract(const Duration(days: 1))))
|
||||
return DateDivider(text: "Yesterday");
|
||||
return DateDivider(date: searchDate);
|
||||
}
|
||||
|
||||
return Divider();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user