From e8f42860af6afde0116228ca8da7cad30fe5de5e Mon Sep 17 00:00:00 2001 From: h7x4 Date: Tue, 3 Aug 2021 22:02:42 +0200 Subject: [PATCH] Add lots of history functionality --- lib/bloc/kanji/kanji_bloc.dart | 28 ++-- lib/bloc/navigation/navigation_bloc.dart | 18 ++ lib/bloc/navigation/navigation_event.dart | 8 + lib/bloc/navigation/navigation_state.dart | 9 + lib/bloc/search/search_bloc.dart | 19 ++- lib/main.dart | 149 +++++++++-------- lib/models/history/kanji_query.dart | 13 ++ lib/models/history/kanji_result.dart | 22 --- lib/models/history/search.dart | 30 ++++ lib/models/history/search_string.dart | 26 --- lib/models/history/word_query.dart | 19 +++ lib/models/history/word_result.dart | 12 +- lib/objectbox-model.json | 155 +++++++++++------- lib/view/components/history/date_divider.dart | 51 ++++++ .../components/history/kanji_search_item.dart | 61 +++++-- .../history/phrase_search_item.dart | 38 +++++ lib/view/components/history/search_item.dart | 60 ++++--- .../components/kanji/kanji_search_bar.dart | 8 +- lib/view/screens/history.dart | 77 ++++++--- 19 files changed, 525 insertions(+), 278 deletions(-) create mode 100644 lib/bloc/navigation/navigation_bloc.dart create mode 100644 lib/bloc/navigation/navigation_event.dart create mode 100644 lib/bloc/navigation/navigation_state.dart create mode 100644 lib/models/history/kanji_query.dart delete mode 100644 lib/models/history/kanji_result.dart create mode 100644 lib/models/history/search.dart delete mode 100644 lib/models/history/search_string.dart create mode 100644 lib/models/history/word_query.dart create mode 100644 lib/view/components/history/date_divider.dart create mode 100644 lib/view/components/history/phrase_search_item.dart diff --git a/lib/bloc/kanji/kanji_bloc.dart b/lib/bloc/kanji/kanji_bloc.dart index 05207b4..505f7db 100644 --- a/lib/bloc/kanji/kanji_bloc.dart +++ b/lib/bloc/kanji/kanji_bloc.dart @@ -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 { - DatabaseBloc _databaseBloc; KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial)); @@ -24,34 +24,32 @@ class KanjiBloc extends Bloc { throw DatabaseNotConnectedException; (_databaseBloc.state as DatabaseConnected) - .database - .box() - .put(KanjiResult( - kanji: kanji, - timestamp: DateTime.now(), - )); + .database + .box() + .put(Search(timestamp: DateTime.now()) + ..kanjiQuery.target = KanjiQuery( + kanji: kanji, + )); } @override - Stream mapEventToState(KanjiEvent event) - async* { + Stream 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); } diff --git a/lib/bloc/navigation/navigation_bloc.dart b/lib/bloc/navigation/navigation_bloc.dart new file mode 100644 index 0000000..bb8852d --- /dev/null +++ b/lib/bloc/navigation/navigation_bloc.dart @@ -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 { + + NavigationBloc() : super(NavigationPage(0)); + + @override + Stream mapEventToState(NavigationEvent event) async* { + if (event is ChangePage) + yield NavigationPage(event.pageNum); + } +} \ No newline at end of file diff --git a/lib/bloc/navigation/navigation_event.dart b/lib/bloc/navigation/navigation_event.dart new file mode 100644 index 0000000..9ab8fa2 --- /dev/null +++ b/lib/bloc/navigation/navigation_event.dart @@ -0,0 +1,8 @@ +abstract class NavigationEvent { + const NavigationEvent(); +} + +class ChangePage extends NavigationEvent { + final int pageNum; + const ChangePage(this.pageNum); +} \ No newline at end of file diff --git a/lib/bloc/navigation/navigation_state.dart b/lib/bloc/navigation/navigation_state.dart new file mode 100644 index 0000000..0f5bda3 --- /dev/null +++ b/lib/bloc/navigation/navigation_state.dart @@ -0,0 +1,9 @@ +abstract class NavigationState { + const NavigationState(); +} + +class NavigationPage extends NavigationState { + final int pageNum; + const NavigationPage(this.pageNum); + +} \ No newline at end of file diff --git a/lib/bloc/search/search_bloc.dart b/lib/bloc/search/search_bloc.dart index 02be4bc..6a0e5fa 100644 --- a/lib/bloc/search/search_bloc.dart +++ b/lib/bloc/search/search_bloc.dart @@ -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 { - DatabaseBloc _databaseBloc; SearchBloc(this._databaseBloc) : super(SearchInitial()); @@ -24,12 +24,12 @@ class SearchBloc extends Bloc { throw DatabaseNotConnectedException; (_databaseBloc.state as DatabaseConnected) - .database - .box() - .put(SearchString( - query: searchString, - timestamp: DateTime.now(), - )); + .database + .box() + .put(Search(timestamp: DateTime.now()) + ..wordQuery.target = WordQuery( + query: searchString, + )); } @override @@ -42,7 +42,8 @@ class SearchBloc extends Bloc { 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'); } diff --git a/lib/main.dart b/lib/main.dart index 3da26d6..161865e 100644 --- a/lib/main.dart +++ b/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 { - int selectedPage = 0; - +class _MyAppState extends State { 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 { @override Widget build(BuildContext context) { - return BlocBuilder( - 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(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( + builder: (context, state) { + int selectedPage = (state as NavigationPage).pageNum; return Scaffold( appBar: AppBar( @@ -89,12 +96,9 @@ class _HomeState extends State { body: pages[selectedPage].content, bottomNavigationBar: BottomNavigationBar( currentIndex: selectedPage, - onTap: (int index) { - setState(() { - selectedPage = index; - }); - }, - items: navBar, + onTap: (int index) => + BlocProvider.of(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 { } } -final List 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), + ), ), ]; diff --git a/lib/models/history/kanji_query.dart b/lib/models/history/kanji_query.dart new file mode 100644 index 0000000..65085f1 --- /dev/null +++ b/lib/models/history/kanji_query.dart @@ -0,0 +1,13 @@ +import 'package:objectbox/objectbox.dart'; + +@Entity() +class KanjiQuery { + int id; + + String kanji; + + KanjiQuery({ + this.id = 0, + required this.kanji, + }); +} diff --git a/lib/models/history/kanji_result.dart b/lib/models/history/kanji_result.dart deleted file mode 100644 index e2beb9d..0000000 --- a/lib/models/history/kanji_result.dart +++ /dev/null @@ -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"; - } -} \ No newline at end of file diff --git a/lib/models/history/search.dart b/lib/models/history/search.dart new file mode 100644 index 0000000..f371573 --- /dev/null +++ b/lib/models/history/search.dart @@ -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(); + + final kanjiQuery = ToOne(); + + 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; + } + +} \ No newline at end of file diff --git a/lib/models/history/search_string.dart b/lib/models/history/search_string.dart deleted file mode 100644 index 1e9cf7c..0000000 --- a/lib/models/history/search_string.dart +++ /dev/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(); - - SearchString({ - required this.timestamp, - required this.query, - }); - - @override - String toString() { - return "[${timestamp.toIso8601String()}] \"$query\""; - } -} \ No newline at end of file diff --git a/lib/models/history/word_query.dart b/lib/models/history/word_query.dart new file mode 100644 index 0000000..6041b00 --- /dev/null +++ b/lib/models/history/word_query.dart @@ -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(); + + WordQuery({ + this.id = 0, + required this.query, + }); +} \ No newline at end of file diff --git a/lib/models/history/word_result.dart b/lib/models/history/word_result.dart index 56f8ec9..75d1f5c 100644 --- a/lib/models/history/word_result.dart +++ b/lib/models/history/word_result.dart @@ -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(); + final searchString = ToOne(); WordResult({ + this.id = 0, required this.timestamp, required this.word, }); - - @override - String toString() { - return "[${timestamp.toIso8601String()}] - $word"; - } } \ No newline at end of file diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index 44a2088..8c9f338 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -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 } \ No newline at end of file diff --git a/lib/view/components/history/date_divider.dart b/lib/view/components/history/date_divider.dart new file mode 100644 index 0000000..2696acc --- /dev/null +++ b/lib/view/components/history/date_divider.dart @@ -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 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, + ), + ); + } +} diff --git a/lib/view/components/history/kanji_search_item.dart b/lib/view/components/history/kanji_search_item.dart index fc60ead..79428cd 100644 --- a/lib/view/components/history/kanji_search_item.dart +++ b/lib/view/components/history/kanji_search_item.dart @@ -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(context).add(ChangePage(1)); + BlocProvider.of(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, + ), ], ); } diff --git a/lib/view/components/history/phrase_search_item.dart b/lib/view/components/history/phrase_search_item.dart new file mode 100644 index 0000000..8cf0252 --- /dev/null +++ b/lib/view/components/history/phrase_search_item.dart @@ -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(context).add(ChangePage(0)); + BlocProvider.of(context).add(GetSearchResults(this.search.query)); + }, + time: timestamp, + search: Text(search.query), + ), + ); + } +} diff --git a/lib/view/components/history/search_item.dart b/lib/view/components/history/search_item.dart index 1ec423b..826a4fa 100644 --- a/lib/view/components/history/search_item.dart +++ b/lib/view/components/history/search_item.dart @@ -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, + ], + ), + ), ); } } diff --git a/lib/view/components/kanji/kanji_search_bar.dart b/lib/view/components/kanji/kanji_search_bar.dart index 62c95f6..ebcbc8a 100644 --- a/lib/view/components/kanji/kanji_search_bar.dart +++ b/lib/view/components/kanji/kanji_search_bar.dart @@ -14,14 +14,12 @@ class KanjiSearchBar extends StatefulWidget { enum TextFieldButton {clear, paste} class _KanjiSearchBarState extends State { - 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 { return TextField( controller: textController, onChanged: (text) => _getKanjiSuggestions(text), - onSubmitted: (text) => {}, - // BlocProvider.of(context).add(GetKanji(text)), + onSubmitted: (_) => {}, decoration: new InputDecoration( - prefixIcon: Icon(Icons.search), hintText: 'Search', fillColor: Colors.white, diff --git a/lib/view/screens/history.dart b/lib/view/screens/history.dart index 1f00b69..124bbed 100644 --- a/lib/view/screens/history.dart +++ b/lib/view/screens/history.dart @@ -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( builder: (context, state) { if (state is DatabaseDisconnected) throw DatabaseNotConnectedException(); - return ListView( - children: (state as DatabaseConnected) - .database - .box() - .getAll() - .map((e) => SearchItem(e) as Widget) - .toList() - + (state as DatabaseConnected) - .database - .box() - .getAll() - .map((e) => KanjiSearchItem(e) as Widget) - .toList(), + return StreamBuilder( + stream: ((state as DatabaseConnected).database.box().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(); + }, + ); + }, ); }, );