diff --git a/lib/bloc/database/database_bloc.dart b/lib/bloc/database/database_bloc.dart new file mode 100644 index 0000000..a5e6efa --- /dev/null +++ b/lib/bloc/database/database_bloc.dart @@ -0,0 +1,25 @@ + +import 'package:flutter_bloc/flutter_bloc.dart'; + +import './database_event.dart'; +import './database_state.dart'; + +export './database_event.dart'; +export './database_state.dart'; +export './database_not_connected_exception.dart'; + +class DatabaseBloc extends Bloc { + + DatabaseBloc() : super(DatabaseDisconnected()); + + @override + Stream mapEventToState(DatabaseEvent event) + async* { + if (event is ConnectedToDatabase) { + yield DatabaseConnected(event.database); + } else { + yield DatabaseDisconnected(); + } + } + +} \ No newline at end of file diff --git a/lib/bloc/database/database_event.dart b/lib/bloc/database/database_event.dart new file mode 100644 index 0000000..0b65487 --- /dev/null +++ b/lib/bloc/database/database_event.dart @@ -0,0 +1,14 @@ +import 'package:objectbox/objectbox.dart'; + +abstract class DatabaseEvent { + const DatabaseEvent(); +} + +class ConnectedToDatabase extends DatabaseEvent { + final Store database; + const ConnectedToDatabase(this.database); +} + +class DisconnectedFromDatabase extends DatabaseEvent { + const DisconnectedFromDatabase(); +} \ No newline at end of file diff --git a/lib/bloc/database/database_not_connected_exception.dart b/lib/bloc/database/database_not_connected_exception.dart new file mode 100644 index 0000000..d062330 --- /dev/null +++ b/lib/bloc/database/database_not_connected_exception.dart @@ -0,0 +1 @@ +class DatabaseNotConnectedException implements Exception {} \ No newline at end of file diff --git a/lib/bloc/database/database_state.dart b/lib/bloc/database/database_state.dart new file mode 100644 index 0000000..042443f --- /dev/null +++ b/lib/bloc/database/database_state.dart @@ -0,0 +1,15 @@ + +import 'package:objectbox/objectbox.dart'; + +abstract class DatabaseState { + const DatabaseState(); +} + +class DatabaseConnected extends DatabaseState { + final Store database; + const DatabaseConnected(this.database); +} + +class DatabaseDisconnected extends DatabaseState { + const DatabaseDisconnected(); +} \ No newline at end of file diff --git a/lib/bloc/kanji/kanji_bloc.dart b/lib/bloc/kanji/kanji_bloc.dart index 3731462..dc82681 100644 --- a/lib/bloc/kanji/kanji_bloc.dart +++ b/lib/bloc/kanji/kanji_bloc.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; +import 'package:jisho_study_tool/models/history/search.dart'; + import './kanji_event.dart'; import './kanji_state.dart'; @@ -12,7 +15,23 @@ export './kanji_state.dart'; class KanjiBloc extends Bloc { - KanjiBloc() : super(KanjiSearch(KanjiSearchType.Initial)); + DatabaseBloc _databaseBloc; + + KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial)); + + void addSearchToDB(searchString) { + if (_databaseBloc.state is DatabaseDisconnected) + throw DatabaseNotConnectedException; + + (_databaseBloc.state as DatabaseConnected) + .database + .box() + .put(Search( + query: searchString, + timestamp: DateTime.now(), + type: "kanji" + )); + } @override Stream mapEventToState(KanjiEvent event) @@ -22,6 +41,7 @@ class KanjiBloc extends Bloc { 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'); diff --git a/lib/bloc/search/search_bloc.dart b/lib/bloc/search/search_bloc.dart index abca65c..8a50031 100644 --- a/lib/bloc/search/search_bloc.dart +++ b/lib/bloc/search/search_bloc.dart @@ -3,8 +3,10 @@ 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.dart'; import 'package:meta/meta.dart'; +import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; import 'package:jisho_study_tool/services/jisho_api/jisho_search.dart'; import 'package:unofficial_jisho_api/parser.dart'; @@ -12,7 +14,24 @@ part 'search_event.dart'; part 'search_state.dart'; class SearchBloc extends Bloc { - SearchBloc() : super(SearchInitial()); + + DatabaseBloc _databaseBloc; + + SearchBloc(this._databaseBloc) : super(SearchInitial()); + + void addSearchToDB(searchString) { + if (_databaseBloc.state is DatabaseDisconnected) + throw DatabaseNotConnectedException; + + (_databaseBloc.state as DatabaseConnected) + .database + .box() + .put(Search( + query: searchString, + timestamp: DateTime.now(), + type: "search" + )); + } @override Stream mapEventToState( @@ -22,6 +41,7 @@ class SearchBloc extends Bloc { yield SearchLoading(); try { + addSearchToDB(event.searchString); final searchResults = await fetchJishoResults(event.searchString); if (searchResults.meta.status == 200) yield SearchFinished(searchResults.data); } on Exception { diff --git a/lib/main.dart b/lib/main.dart index de77b09..822251d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/search/search_bloc.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/view/screens/kanji/view.dart'; import 'package:jisho_study_tool/view/screens/history.dart'; import 'package:jisho_study_tool/view/screens/search/view.dart'; -import 'bloc/search/search_bloc.dart'; void main() => runApp(MyApp()); +DatabaseBloc _databaseBloc = DatabaseBloc(); + class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { @@ -21,8 +28,9 @@ class MyApp extends StatelessWidget { ), home: MultiBlocProvider( providers: [ - BlocProvider(create: (context) => SearchBloc()), - BlocProvider(create: (context) => KanjiBloc()), + BlocProvider(create: (context) => SearchBloc(_databaseBloc)), + BlocProvider(create: (context) => KanjiBloc(_databaseBloc)), + BlocProvider(create: (context) => _databaseBloc), ], child: Home(), ), @@ -38,27 +46,62 @@ class Home extends StatefulWidget { class _HomeState extends State { int selectedPage = 0; + Store _store; + + @override + void initState() { + super.initState(); + + getApplicationDocumentsDirectory() + .then((dir) { + _store = Store( + getObjectBoxModel(), + directory: join(dir.path, 'objectbox'), + ); + + _databaseBloc.add(ConnectedToDatabase(_store)); + }); + } + + @override + void dispose() { + _store.close(); + _databaseBloc.add(DisconnectedFromDatabase()); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: pages[selectedPage].titleBar, - centerTitle: true, - ), - body: pages[selectedPage].content, - bottomNavigationBar: BottomNavigationBar( - currentIndex: selectedPage, - onTap: (int index) { - setState(() { - selectedPage = index; - }); - }, - items: navBar, - showSelectedLabels: false, - showUnselectedLabels: false, - unselectedItemColor: Colors.blue, - selectedItemColor: Colors.green, - ), + return BlocBuilder( + builder: (context, state) { + + if (state is DatabaseDisconnected) { + return Center( + child: CircularProgressIndicator(), + ); + } + + return Scaffold( + appBar: AppBar( + title: pages[selectedPage].titleBar, + centerTitle: true, + ), + body: pages[selectedPage].content, + bottomNavigationBar: BottomNavigationBar( + currentIndex: selectedPage, + onTap: (int index) { + setState(() { + selectedPage = index; + }); + }, + items: navBar, + showSelectedLabels: false, + showUnselectedLabels: false, + unselectedItemColor: Colors.blue, + selectedItemColor: Colors.green, + ), + ); + }, ); } } diff --git a/lib/models/history/search.dart b/lib/models/history/search.dart new file mode 100644 index 0000000..4211153 --- /dev/null +++ b/lib/models/history/search.dart @@ -0,0 +1,26 @@ +import 'package:objectbox/objectbox.dart'; + +@Entity() +class Search { + int id = 0; + + @Property(type: PropertyType.date) + DateTime timestamp; + + String query; + + String type; + + Search({ + this.id, + this.timestamp, + this.query, + this.type, + }); + + @override + String toString() { + return "${timestamp.toIso8601String()} [${type.toUpperCase()}] - $query"; + } + +} \ No newline at end of file diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json new file mode 100644 index 0000000..ae67043 --- /dev/null +++ b/lib/objectbox-model.json @@ -0,0 +1,47 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_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:7471000004971513655", + "lastPropertyId": "4:1530573958565295186", + "name": "Search", + "properties": [ + { + "id": "1:182004738902401315", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:647032929519296287", + "name": "timestamp", + "type": 10 + }, + { + "id": "3:8448353731705407210", + "name": "query", + "type": 9 + }, + { + "id": "4:1530573958565295186", + "name": "type", + "type": 9 + } + ], + "relations": [] + } + ], + "lastEntityId": "1:7471000004971513655", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/lib/view/screens/history.dart b/lib/view/screens/history.dart index 0b16214..ebaf41f 100644 --- a/lib/view/screens/history.dart +++ b/lib/view/screens/history.dart @@ -1,10 +1,27 @@ 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/search.dart'; class HistoryView extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView.builder( - itemBuilder: (context, index) => ListTile(), + // return ListView.builder( + // itemBuilder: (context, index) => ListTile(), + // ); + return BlocBuilder( + builder: (context, state) { + if (state is DatabaseDisconnected) + throw DatabaseNotConnectedException(); + return Text( + (state as DatabaseConnected) + .database + .box() + .getAll() + .map((e) => e.toString()) + .toString() + ); + }, ); } } diff --git a/pubspec.lock b/pubspec.lock index 5d824bb..62ace01 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,13 +1,34 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "22.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.7.0" bloc: dependency: transitive description: @@ -22,6 +43,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.7" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.12.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.12" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.1" characters: dependency: transitive description: @@ -36,6 +113,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" clock: dependency: transitive description: @@ -43,6 +134,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" collection: dependency: transitive description: @@ -50,6 +148,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" csslib: dependency: transitive description: @@ -57,6 +169,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" division: dependency: "direct main" description: @@ -85,6 +204,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.0" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -107,6 +233,20 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" html: dependency: transitive description: @@ -128,6 +268,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" http_parser: dependency: transitive description: @@ -135,6 +282,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -142,6 +296,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: @@ -162,7 +330,14 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" nested: dependency: transitive description: @@ -170,6 +345,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + objectbox: + dependency: "direct main" + description: + name: objectbox + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + objectbox_flutter_libs: + dependency: "direct main" + description: + name: objectbox_flutter_libs + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + objectbox_generator: + dependency: "direct dev" + description: + name: objectbox_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -177,6 +380,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" path_provider_linux: dependency: transitive description: @@ -184,6 +394,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" path_provider_platform_interface: dependency: transitive description: @@ -219,6 +436,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" process: dependency: transitive description: @@ -233,6 +457,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" shared_preferences: dependency: "direct main" description: @@ -275,11 +513,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.4+1" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" source_span: dependency: transitive description: @@ -301,6 +560,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: @@ -321,7 +587,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.0" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -385,6 +658,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" win32: dependency: transitive description: @@ -399,6 +686,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" - flutter: ">=1.22.0" + flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 25c1638..1f98988 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,9 @@ dependencies: sdk: flutter shared_preferences: "^2.0.3" + objectbox: ^1.1.1 + objectbox_flutter_libs: any + path_provider: ^2.0.2 # cupertino_icons: ^0.1.2 mdi: ^4.0.0 @@ -21,6 +24,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + build_runner: ^1.0.0 + objectbox_generator: any flutter: uses-material-design: true