diff --git a/.gitignore b/.gitignore index 0bd996a..d16b8eb 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,6 @@ .pub/ /build/ -objectbox.g.dart - # Web related lib/generated_plugin_registrant.dart diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..894e0e4 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,167 @@ +analyzer: + exclude: + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + # - always_put_control_body_on_new_line # Allow return statements on same line as ifs + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + # - always_specify_types + # - always_use_package_imports # we do this commonly + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_classes_with_only_static_members + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + - avoid_implementing_value_types + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_multiple_declarations_per_line + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_future + - avoid_returning_null_for_void + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_types_on_closure_parameters + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - cast_nullable_to_non_nullable + - control_flow_in_finally + # - curly_braces_in_flow_control_structures + - depend_on_referenced_packages + - deprecated_consistency + - directives_ordering + - do_not_use_environment + - empty_catches + - empty_constructor_bodies + - empty_statements + - eol_at_end_of_file + - exhaustive_cases + - file_names + # - flutter_style_todos # This is mostly a single person project for the time being + - hash_and_equals + - implementation_imports + - iterable_contains_unrelated_type + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - library_private_types_in_public_api + # - lines_longer_than_80_chars + - list_remove_unrelated_type + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_default_cases + - no_duplicate_case_values + - no_logic_in_create_state + # - non_constant_identifier_names # use API names for several variables + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + # - prefer_final_parameters + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + # - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_null_aware_operators + - prefer_relative_imports + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - require_trailing_commas + - sized_box_for_whitespace + - slash_for_doc_comments + - sort_child_properties_last + # - sort_constructors_first + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_checks + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + - unsafe_html + - use_build_context_synchronously + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_test_throws_matchers + - valid_regexps + - void_checks + \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 965d966..87e8925 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -27,4 +27,5 @@ android:name="flutterEmbedding" android:value="2" /> + diff --git a/android/build.gradle b/android/build.gradle index 3100ad2..f97badc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.0' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:7.0.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146..595fb86 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/lib/bloc/database/database_bloc.dart b/lib/bloc/database/database_bloc.dart deleted file mode 100644 index 37d338c..0000000 --- a/lib/bloc/database/database_bloc.dart +++ /dev/null @@ -1,27 +0,0 @@ - -import 'package:bloc/bloc.dart'; - -import './database_event.dart'; -import './database_state.dart'; - -export 'package:flutter_bloc/flutter_bloc.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 deleted file mode 100644 index 0b65487..0000000 --- a/lib/bloc/database/database_event.dart +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index d062330..0000000 --- a/lib/bloc/database/database_not_connected_exception.dart +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 042443f..0000000 --- a/lib/bloc/database/database_state.dart +++ /dev/null @@ -1,15 +0,0 @@ - -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/history/history_bloc.dart b/lib/bloc/history/history_bloc.dart deleted file mode 100644 index 93e33f7..0000000 --- a/lib/bloc/history/history_bloc.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:meta/meta.dart'; - -export 'package:flutter_bloc/flutter_bloc.dart'; - -part 'history_event.dart'; -part 'history_state.dart'; - -class HistoryBloc extends Bloc { - HistoryBloc() : super(HistoryInitial()); - - @override - Stream mapEventToState( - HistoryEvent event, - ) async* { - // TODO: implement mapEventToState - } -} diff --git a/lib/bloc/history/history_event.dart b/lib/bloc/history/history_event.dart deleted file mode 100644 index 40ede67..0000000 --- a/lib/bloc/history/history_event.dart +++ /dev/null @@ -1,4 +0,0 @@ -part of 'history_bloc.dart'; - -@immutable -abstract class HistoryEvent {} diff --git a/lib/bloc/history/history_state.dart b/lib/bloc/history/history_state.dart deleted file mode 100644 index 57b69b2..0000000 --- a/lib/bloc/history/history_state.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'history_bloc.dart'; - -@immutable -abstract class HistoryState {} - -class HistoryInitial extends HistoryState {} diff --git a/lib/bloc/kanji/kanji_bloc.dart b/lib/bloc/kanji/kanji_bloc.dart deleted file mode 100644 index 0533b9c..0000000 --- a/lib/bloc/kanji/kanji_bloc.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'dart:async'; - -import 'package:jisho_study_tool/bloc/database/database_bloc.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'; - -import 'package:bloc/bloc.dart'; -import 'package:jisho_study_tool/services/jisho_api/kanji_search.dart'; -import 'package:jisho_study_tool/services/kanji_suggestions.dart'; - -export 'package:flutter_bloc/flutter_bloc.dart'; - -export './kanji_event.dart'; -export './kanji_state.dart'; - -class KanjiBloc extends Bloc { - DatabaseBloc _databaseBloc; - - KanjiBloc(this._databaseBloc) : super(KanjiSearch(KanjiSearchType.Initial)); - - void addSearchToDB(kanji) { - if (_databaseBloc.state is DatabaseDisconnected) - throw DatabaseNotConnectedException; - - (_databaseBloc.state as DatabaseConnected) - .database - .box() - .put(Search(timestamp: DateTime.now()) - ..kanjiQuery.target = KanjiQuery( - kanji: kanji, - )); - } - - @override - 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'); - } 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/kanji/kanji_event.dart b/lib/bloc/kanji/kanji_event.dart deleted file mode 100644 index 7bb79b8..0000000 --- a/lib/bloc/kanji/kanji_event.dart +++ /dev/null @@ -1,17 +0,0 @@ -abstract class KanjiEvent { - const KanjiEvent(); -} - -class GetKanjiSuggestions extends KanjiEvent { - final String searchString; - const GetKanjiSuggestions(this.searchString); -} - -class GetKanji extends KanjiEvent { - final String kanjiSearchString; - const GetKanji(this.kanjiSearchString); -} - -class ReturnToInitialState extends KanjiEvent { - const ReturnToInitialState(); -} \ No newline at end of file diff --git a/lib/bloc/kanji/kanji_state.dart b/lib/bloc/kanji/kanji_state.dart deleted file mode 100644 index ecc3653..0000000 --- a/lib/bloc/kanji/kanji_state.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:unofficial_jisho_api/api.dart'; - -abstract class KanjiState { - const KanjiState(); -} - -enum KanjiSearchType { - Initial, - Keyboard, - Drawing, - Radical, - Grade -} - -class KanjiSearch extends KanjiState { - final KanjiSearchType type; - const KanjiSearch(this.type); -} - -class KanjiSearchKeyboard extends KanjiSearch { - final List kanjiSuggestions; - const KanjiSearchKeyboard(KanjiSearchType type, this.kanjiSuggestions) : super(type); -} - -class KanjiSearchLoading extends KanjiState { - const KanjiSearchLoading(); -} - -class KanjiSearchFinished extends KanjiState { - final KanjiResult kanji; - final bool starred; - - const KanjiSearchFinished({ - required this.kanji, - this.starred = false, - }); -} - -class KanjiSearchError extends KanjiState { - final String message; - - const KanjiSearchError(this.message); -} \ No newline at end of file diff --git a/lib/bloc/navigation/navigation_bloc.dart b/lib/bloc/navigation/navigation_bloc.dart deleted file mode 100644 index f48fc48..0000000 --- a/lib/bloc/navigation/navigation_bloc.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:bloc/bloc.dart'; - -import './navigation_event.dart'; -import './navigation_state.dart'; - -export 'package:flutter_bloc/flutter_bloc.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 deleted file mode 100644 index 9ab8fa2..0000000 --- a/lib/bloc/navigation/navigation_event.dart +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 0f5bda3..0000000 --- a/lib/bloc/navigation/navigation_state.dart +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 24498cc..0000000 --- a/lib/bloc/search/search_bloc.dart +++ /dev/null @@ -1,56 +0,0 @@ -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:jisho_study_tool/models/history/word_query.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'; - -export 'package:flutter_bloc/flutter_bloc.dart'; - -part 'search_event.dart'; -part 'search_state.dart'; - -class SearchBloc extends Bloc { - 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(timestamp: DateTime.now()) - ..wordQuery.target = WordQuery( - query: searchString, - )); - } - - @override - Stream mapEventToState( - SearchEvent event, - ) async* { - if (event is GetSearchResults) { - yield SearchLoading(); - - try { - addSearchToDB(event.searchString); - final searchResults = await fetchJishoResults(event.searchString); - if (searchResults.meta.status == 200) - yield SearchFinished(searchResults.data!); - } on Exception { - yield SearchError('Something went wrong'); - } - } else if (event is ReturnToInitialState) { - yield SearchInitial(); - } - } -} diff --git a/lib/bloc/search/search_event.dart b/lib/bloc/search/search_event.dart deleted file mode 100644 index 9b5d3f2..0000000 --- a/lib/bloc/search/search_event.dart +++ /dev/null @@ -1,15 +0,0 @@ -part of 'search_bloc.dart'; - -@immutable -abstract class SearchEvent { - const SearchEvent(); -} - -class GetSearchResults extends SearchEvent { - final String searchString; - const GetSearchResults(this.searchString); -} - -class ReturnToInitialState extends SearchEvent { - const ReturnToInitialState(); -} \ No newline at end of file diff --git a/lib/bloc/search/search_state.dart b/lib/bloc/search/search_state.dart deleted file mode 100644 index 4f934ee..0000000 --- a/lib/bloc/search/search_state.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of 'search_bloc.dart'; - -@immutable -abstract class SearchState { - const SearchState(); -} - -class SearchInitial extends SearchState { - const SearchInitial(); -} - -class SearchLoading extends SearchState { - const SearchLoading(); -} - -class SearchFinished extends SearchState { - final List results; - - const SearchFinished(this.results); -} - -class SearchError extends SearchState { - final String message; - - const SearchError(this.message); -} \ No newline at end of file diff --git a/lib/bloc/theme/theme_bloc.dart b/lib/bloc/theme/theme_bloc.dart index 49167b7..b34b5a7 100644 --- a/lib/bloc/theme/theme_bloc.dart +++ b/lib/bloc/theme/theme_bloc.dart @@ -1,10 +1,10 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; -import 'package:meta/meta.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../models/themes/theme.dart'; + export 'package:flutter_bloc/flutter_bloc.dart'; export 'package:jisho_study_tool/models/themes/theme.dart'; @@ -12,24 +12,20 @@ part 'theme_event.dart'; part 'theme_state.dart'; class ThemeBloc extends Bloc { - bool prefsAreLoaded = false; + ThemeBloc() : super(const LightThemeState()) { + on( + (event, emit) => emit( + event.themeIsDark ? const DarkThemeState() : const LightThemeState(), + ), + ); - ThemeBloc() : super(LightThemeState()) { - SharedPreferences.getInstance().then((prefs) { - this.prefsAreLoaded = true; - this.add( - SetTheme( - themeIsDark: prefs.getBool('darkThemeEnabled') ?? false, - ), - ); - }); - } - - @override - Stream mapEventToState(ThemeEvent event) async* { - if (event is SetTheme) - yield event.themeIsDark - ? DarkThemeState(prefsAreLoaded: prefsAreLoaded) - : LightThemeState(prefsAreLoaded: prefsAreLoaded); + add( + SetTheme( + themeIsDark: GetIt.instance + .get() + .getBool('darkThemeEnabled') ?? + false, + ), + ); } } diff --git a/lib/bloc/theme/theme_state.dart b/lib/bloc/theme/theme_state.dart index d4bb021..134e0c6 100644 --- a/lib/bloc/theme/theme_state.dart +++ b/lib/bloc/theme/theme_state.dart @@ -2,25 +2,21 @@ part of 'theme_bloc.dart'; @immutable abstract class ThemeState { - final bool prefsAreLoaded; - - const ThemeState(this.prefsAreLoaded); + const ThemeState(); AppTheme get theme; } class LightThemeState extends ThemeState { - final bool prefsAreLoaded; - - const LightThemeState({this.prefsAreLoaded = false}) : super(prefsAreLoaded); + const LightThemeState(); + @override AppTheme get theme => LightTheme(); } class DarkThemeState extends ThemeState { - final bool prefsAreLoaded; - - const DarkThemeState({this.prefsAreLoaded = false}) : super(prefsAreLoaded); + const DarkThemeState(); + @override AppTheme get theme => DarkTheme(); -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 8210f71..31529b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,180 +1,57 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; -import 'package:jisho_study_tool/view/screens/splash.dart'; -import 'package:mdi/mdi.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:get_it/get_it.dart'; import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sembast/sembast.dart'; +import 'package:sembast/sembast_io.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -import 'package:jisho_study_tool/objectbox.g.dart'; +import 'bloc/theme/theme_bloc.dart'; +import 'router.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'; -import 'package:jisho_study_tool/view/screens/search/view.dart'; -import 'package:jisho_study_tool/view/screens/settings.dart'; - -import 'models/themes/theme.dart'; - -void main() => runApp(MyApp()); - -DatabaseBloc _databaseBloc = DatabaseBloc(); - -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); +Future setupDatabase() async { + final Directory appDocDir = await getApplicationDocumentsDirectory(); + if (!appDocDir.existsSync()) appDocDir.createSync(recursive: true); + final Database database = + await databaseFactoryIo.openDatabase(join(appDocDir.path, 'sembast.db')); + GetIt.instance.registerSingleton(database); } -class _MyAppState extends State { - late final Store _store; - bool dbConnected = false; +Future setupSharedPreferences() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + GetIt.instance.registerSingleton(prefs); +} - @override - void initState() { - super.initState(); +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); - getApplicationDocumentsDirectory().then((dir) { - _store = Store( - getObjectBoxModel(), - directory: join(dir.path, 'objectbox'), - ); + await Future.wait([ + setupDatabase(), + setupSharedPreferences(), + ]); - _databaseBloc.add(ConnectedToDatabase(_store)); - setState(() { - dbConnected = true; - }); - }); - } + runApp(const MyApp()); +} - @override - void dispose() { - _store.close(); - _databaseBloc.add(DisconnectedFromDatabase()); - super.dispose(); - } +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => SearchBloc(_databaseBloc)), - BlocProvider(create: (context) => KanjiBloc(_databaseBloc)), - BlocProvider(create: (context) => _databaseBloc), - BlocProvider(create: (context) => NavigationBloc()), BlocProvider(create: (context) => ThemeBloc()), ], child: BlocBuilder( - builder: (context, themeState) { - return MaterialApp( - title: 'Jisho Study Tool', - theme: themeState.theme.getMaterialTheme(), - home: dbConnected && themeState.prefsAreLoaded - ? Home() - : SplashScreen(), - ); - }, + builder: (context, themeState) => MaterialApp( + title: 'Jisho Study Tool', + theme: themeState.theme.getMaterialTheme(), + initialRoute: '/', + onGenerateRoute: generateRoute, + ), ), ); } } - -class Home extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, navigationState) { - int selectedPage = (navigationState as NavigationPage).pageNum; - return BlocBuilder( - builder: (context, themeState) { - return Scaffold( - appBar: AppBar( - title: pages[selectedPage].titleBar, - centerTitle: true, - backgroundColor: AppTheme.jishoGreen.background, - foregroundColor: AppTheme.jishoGreen.foreground, - ), - body: Stack( - children: [ - Positioned( - child: Image.asset( - 'assets/images/denshi_jisho_background_overlay.png'), - right: 30, - left: 100, - bottom: 30, - ), - pages[selectedPage].content, - ], - ), - bottomNavigationBar: BottomNavigationBar( - fixedColor: AppTheme.jishoGreen.background, - currentIndex: selectedPage, - onTap: (int index) => BlocProvider.of(context) - .add(ChangePage(index)), - items: pages.map((p) => p.item).toList(), - showSelectedLabels: false, - showUnselectedLabels: false, - unselectedItemColor: themeState.theme.menuGreyDark.background, - ), - ); - }, - ); - }, - ); - } -} - -class _Page { - final Widget content; - final Widget titleBar; - final BottomNavigationBarItem item; - - const _Page({ - required this.content, - required this.titleBar, - required this.item, - }); -} - -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("Saved"), - item: BottomNavigationBarItem( - label: 'Saved', - icon: Icon(Icons.bookmark), - ), - ), - _Page( - content: SettingsView(), - 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 index 65085f1..56d2c00 100644 --- a/lib/models/history/kanji_query.dart +++ b/lib/models/history/kanji_query.dart @@ -1,13 +1,12 @@ -import 'package:objectbox/objectbox.dart'; - -@Entity() class KanjiQuery { - int id; - - String kanji; + final String kanji; KanjiQuery({ - this.id = 0, required this.kanji, }); + + Map toJson() => {'kanji': kanji}; + + factory KanjiQuery.fromJson(Map json) => + KanjiQuery(kanji: json['kanji'] as String); } diff --git a/lib/models/history/search.dart b/lib/models/history/search.dart index f371573..2de2096 100644 --- a/lib/models/history/search.dart +++ b/lib/models/history/search.dart @@ -1,30 +1,42 @@ -import 'package:objectbox/objectbox.dart'; +import 'package:sembast/sembast.dart'; import './kanji_query.dart'; import './word_query.dart'; -@Entity() class Search { - int id; + final DateTime timestamp; + final WordQuery? wordQuery; + final KanjiQuery? kanjiQuery; - @Property(type: PropertyType.date) - late final DateTime timestamp; + Search.fromKanjiQuery({ + required this.timestamp, + required KanjiQuery this.kanjiQuery, + }) : wordQuery = null; - final wordQuery = ToOne(); + Search.fromWordQuery({ + required this.timestamp, + required WordQuery this.wordQuery, + }) : kanjiQuery = null; - final kanjiQuery = ToOne(); + bool get isKanji => wordQuery == null; - Search({ - this.id = 0, - required this.timestamp - }); // { + Map toJson() => { + 'timestamp': timestamp.millisecondsSinceEpoch, + 'wordQuery': wordQuery?.toJson(), + 'kanjiQuery': kanjiQuery?.toJson(), + }; - bool isKanji() { - // // TODO: better error message - if (this.wordQuery.target == null && this.kanjiQuery.target == null) - throw Exception(); - - return this.wordQuery.target == null; - } + factory Search.fromJson(Map json) => + json['wordQuery'] != null + ? Search.fromWordQuery( + timestamp: + DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int), + wordQuery: WordQuery.fromJson(json['wordQuery']), + ) + : Search.fromKanjiQuery( + timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int), + kanjiQuery: KanjiQuery.fromJson(json['kanjiQuery']), + ); -} \ No newline at end of file + static StoreRef get store => intMapStoreFactory.store('search'); +} diff --git a/lib/models/history/word_query.dart b/lib/models/history/word_query.dart index 6041b00..e0904e8 100644 --- a/lib/models/history/word_query.dart +++ b/lib/models/history/word_query.dart @@ -1,19 +1,17 @@ -import 'package:objectbox/objectbox.dart'; - import './word_result.dart'; -@Entity() class WordQuery { - int id; - - String query; + final String query; // TODO: Link query with results that the user clicks onto. - @Backlink() - final chosenResults = ToMany(); + // final List chosenResults; WordQuery({ - this.id = 0, required this.query, }); -} \ No newline at end of file + + Map toJson() => {'query': query}; + + factory WordQuery.fromJson(Map json) => + WordQuery(query: json['query'] as String); +} diff --git a/lib/models/history/word_result.dart b/lib/models/history/word_result.dart index 75d1f5c..a7c23b2 100644 --- a/lib/models/history/word_result.dart +++ b/lib/models/history/word_result.dart @@ -1,21 +1,13 @@ -import 'package:objectbox/objectbox.dart'; +import 'word_query.dart'; -import 'package:jisho_study_tool/models/history/word_query.dart'; - -@Entity() class WordResult { - int id; - - @Property(type: PropertyType.date) - DateTime timestamp; - - String word; - - final searchString = ToOne(); + final DateTime timestamp; + final String word; + final WordQuery searchString; WordResult({ - this.id = 0, required this.timestamp, required this.word, + required this.searchString, }); -} \ No newline at end of file +} diff --git a/lib/models/storage/common.dart b/lib/models/storage/common.dart new file mode 100644 index 0000000..6aeeb7c --- /dev/null +++ b/lib/models/storage/common.dart @@ -0,0 +1,22 @@ +// import 'package:objectbox/objectbox.dart'; +// import 'package:unofficial_jisho_api/api.dart' as jisho; + +// TODO: Rewrite for sembast + +// @Entity() +// class ExampleSentencePiece { +// int id; +// String? lifted; +// String unlifted; + +// ExampleSentencePiece({ +// this.id = 0, +// required this.lifted, +// required this.unlifted, +// }); + +// ExampleSentencePiece.fromJishoObject(jisho.ExampleSentencePiece object) +// : id = 0, +// lifted = object.lifted, +// unlifted = object.unlifted; +// } diff --git a/lib/models/storage/example.dart b/lib/models/storage/example.dart new file mode 100644 index 0000000..58e7d2c --- /dev/null +++ b/lib/models/storage/example.dart @@ -0,0 +1,58 @@ +// import 'package:objectbox/objectbox.dart'; +// import 'package:unofficial_jisho_api/api.dart' as jisho; + +// import 'common.dart'; + +// TODO: Rewrite for sembast + +// @Entity() +// class ExampleResultData { +// int id; +// String kanji; +// String kana; +// String english; +// List pieces; + +// ExampleResultData({ +// this.id = 0, +// required this.kanji, +// required this.kana, +// required this.english, +// required this.pieces, +// }); + +// ExampleResultData.fromJishoObject(jisho.ExampleResultData object) +// : id = 0, +// kanji = object.kanji, +// kana = object.kana, +// english = object.english, +// pieces = object.pieces +// .map((p) => ExampleSentencePiece.fromJishoObject(p)) +// .toList(); +// } + +// @Entity() +// class ExampleResults { +// int id; +// String query; +// bool found; +// String uri; +// List results; + +// ExampleResults({ +// this.id = 0, +// required this.query, +// required this.found, +// required this.uri, +// required this.results, +// }); + +// ExampleResults.fromJishoObject(jisho.ExampleResults object) +// : id = 0, +// query = object.query, +// found = object.found, +// uri = object.uri, +// results = object.results +// .map((r) => ExampleResultData.fromJishoObject(r)) +// .toList(); +// } diff --git a/lib/models/storage/kanji_result.dart b/lib/models/storage/kanji_result.dart new file mode 100644 index 0000000..28f324b --- /dev/null +++ b/lib/models/storage/kanji_result.dart @@ -0,0 +1,129 @@ +// import 'package:objectbox/objectbox.dart'; +// import 'package:unofficial_jisho_api/api.dart' as jisho; + +// TODO: Rewrite for sembast + +// @Entity() +// class YomiExample { +// int id; +// String example; +// String reading; +// String meaning; + +// YomiExample({ +// this.id = 0, +// required this.example, +// required this.reading, +// required this.meaning, +// }); + +// YomiExample.fromJishoObject(jisho.YomiExample object) +// : id = 0, +// example = object.example, +// reading = object.reading, +// meaning = object.meaning; +// } + +// @Entity() +// class Radical { +// int id = 0; +// String symbol; +// List forms; +// String meaning; + +// Radical({ +// this.id = 0, +// required this.symbol, +// required this.forms, +// required this.meaning, +// }); + +// Radical.fromJishoObject(jisho.Radical object) +// : symbol = object.symbol, +// forms = object.forms, +// meaning = object.meaning; +// } + +// @Entity() +// class KanjiResult { +// int id = 0; +// String query; +// bool found; +// KanjiResultData? data; + +// KanjiResult({ +// this.id = 0, +// required this.query, +// required this.found, +// required this.data, +// }); + +// KanjiResult.fromJishoObject(jisho.KanjiResult object) +// : query = object.query, +// found = object.found, +// data = (object.data == null) +// ? null +// : KanjiResultData.fromJishoObject(object.data!); +// } + +// @Entity() +// class KanjiResultData { +// int id = 0; +// String? taughtIn; +// String? jlptLevel; +// int? newspaperFrequencyRank; +// int strokeCount; +// String meaning; +// List kunyomi; +// List onyomi; +// List kunyomiExamples; +// List onyomiExamples; +// Radical? radical; +// List parts; +// String strokeOrderDiagramUri; +// String strokeOrderSvgUri; +// String strokeOrderGifUri; +// String uri; + +// KanjiResultData({ +// this.id = 0, +// required this.taughtIn, +// required this.jlptLevel, +// required this.newspaperFrequencyRank, +// required this.strokeCount, +// required this.meaning, +// required this.kunyomi, +// required this.onyomi, +// required this.kunyomiExamples, +// required this.onyomiExamples, +// required this.radical, +// required this.parts, +// required this.strokeOrderDiagramUri, +// required this.strokeOrderSvgUri, +// required this.strokeOrderGifUri, +// required this.uri, +// }); + +// KanjiResultData.fromJishoObject(jisho.KanjiResultData object) +// : taughtIn = object.taughtIn, +// jlptLevel = object.jlptLevel, +// newspaperFrequencyRank = object.newspaperFrequencyRank, +// strokeCount = object.strokeCount, +// meaning = object.meaning, +// kunyomi = object.kunyomi, +// onyomi = object.onyomi, +// kunyomiExamples = object.kunyomiExamples +// .map((k) => YomiExample.fromJishoObject(k)) +// .toList(), +// onyomiExamples = object.onyomiExamples +// .map((o) => YomiExample.fromJishoObject(o)) +// .toList(), +// radical = (object.radical == null) +// ? null +// : Radical.fromJishoObject(object.radical!), +// parts = object.parts, +// strokeOrderDiagramUri = object.strokeOrderDiagramUri, +// strokeOrderSvgUri = object.strokeOrderSvgUri, +// strokeOrderGifUri = object.strokeOrderGifUri, +// uri = object.uri; +// } diff --git a/lib/models/storage/scrape_result.dart b/lib/models/storage/scrape_result.dart new file mode 100644 index 0000000..c02eae7 --- /dev/null +++ b/lib/models/storage/scrape_result.dart @@ -0,0 +1,155 @@ +// import 'package:objectbox/objectbox.dart'; +// import 'package:unofficial_jisho_api/api.dart' as jisho; + +// import 'common.dart'; + +// TODO: Rewrite for sembast + +// @Entity() +// class PhraseScrapeSentence { +// int id; +// String english; +// String japanese; +// List pieces; + +// PhraseScrapeSentence({ +// this.id = 0, +// required this.english, +// required this.japanese, +// required this.pieces, +// }); + +// PhraseScrapeSentence.fromJishoObject(jisho.PhraseScrapeSentence object) +// : id = 0, +// english = object.english, +// japanese = object.japanese, +// pieces = object.pieces +// .map((p) => ExampleSentencePiece.fromJishoObject(p)) +// .toList(); +// } + +// @Entity() +// class PhraseScrapeMeaning { +// int id; +// List seeAlsoTerms; +// List sentences; +// String definition; +// List supplemental; +// String? definitionAbstract; +// List tags; + +// PhraseScrapeMeaning({ +// this.id = 0, +// required this.seeAlsoTerms, +// required this.sentences, +// required this.definition, +// required this.supplemental, +// required this.definitionAbstract, +// required this.tags, +// }); + +// PhraseScrapeMeaning.fromJishoObject(jisho.PhraseScrapeMeaning object) +// : id = 0, +// seeAlsoTerms = object.seeAlsoTerms, +// sentences = object.sentences +// .map((s) => PhraseScrapeSentence.fromJishoObject(s)) +// .toList(), +// definition = object.definition, +// supplemental = object.supplemental, +// definitionAbstract = object.definitionAbstract, +// tags = object.tags; +// } + +// @Entity() +// class KanjiKanaPair { +// int id; +// String kanji; +// String? kana; + +// KanjiKanaPair({ +// this.id = 0, +// required this.kanji, +// required this.kana, +// }); + +// KanjiKanaPair.fromJishoObject(jisho.KanjiKanaPair object) +// : id = 0, +// kanji = object.kanji, +// kana = object.kana; +// } + +// @Entity() +// class PhrasePageScrapeResult { +// int id; +// bool found; +// String query; +// PhrasePageScrapeResultData? data; + +// PhrasePageScrapeResult({ +// this.id = 0, +// required this.found, +// required this.query, +// required this.data, +// }); + +// PhrasePageScrapeResult.fromJishoObject(jisho.PhrasePageScrapeResult object) +// : id = 0, +// found = object.found, +// query = object.query, +// data = (object.data == null) +// ? null +// : PhrasePageScrapeResultData.fromJishoObject(object.data!); +// } + +// @Entity() +// class AudioFile { +// int id; +// String uri; +// String mimetype; + +// AudioFile({ +// this.id = 0, +// required this.uri, +// required this.mimetype, +// }); + +// AudioFile.fromJishoObject(jisho.AudioFile object) +// : id = 0, +// uri = object.uri, +// mimetype = object.mimetype; +// } + +// @Entity() +// class PhrasePageScrapeResultData { +// int id; +// String uri; +// List tags; +// List meanings; +// List otherForms; +// List audio; +// List notes; + +// PhrasePageScrapeResultData({ +// this.id = 0, +// required this.uri, +// required this.tags, +// required this.meanings, +// required this.otherForms, +// required this.audio, +// required this.notes, +// }); + +// PhrasePageScrapeResultData.fromJishoObject( +// jisho.PhrasePageScrapeResultData object, +// ) : id = 0, +// uri = object.uri, +// tags = object.tags, +// meanings = object.meanings +// .map((m) => PhraseScrapeMeaning.fromJishoObject(m)) +// .toList(), +// otherForms = object.otherForms +// .map((f) => KanjiKanaPair.fromJishoObject(f)) +// .toList(), +// audio = object.audio.map((a) => AudioFile.fromJishoObject(a)).toList(), +// notes = object.notes; +// } diff --git a/lib/models/storage/search_result.dart b/lib/models/storage/search_result.dart index 7d1e5d3..3f6738b 100644 --- a/lib/models/storage/search_result.dart +++ b/lib/models/storage/search_result.dart @@ -1,72 +1,195 @@ // import 'package:objectbox/objectbox.dart'; // import 'package:unofficial_jisho_api/api.dart' as jisho; +// TODO: Rewrite for sembast + // @Entity() // class SearchResult { -// int id = 0; -// final meta = ToOne(); -// final data = ToMany(); +// int id; +// final JishoResultMeta meta; +// final ToMany data; -// // SearchResult(JishoAPIResult result) { -// // this.data = result.data; -// // this.meta = result.meta; -// // } +// SearchResult({ +// this.id = 0, +// required this.meta, +// required this.data, +// }); -// // JishoAPIResult toJishoAPIResult() { -// // return JishoAPIResult(meta: this.meta, data: this.data); -// // } +// SearchResult.fromJishoObject(final jisho.JishoAPIResult object) +// : id = 0, +// meta = JishoResultMeta.fromJishoObject(object.meta), +// data = ToMany() +// ..addAll( +// object.data?.map((r) => JishoResult.fromJishoObject(r)) ?? +// [], +// ); // } // @Entity() // class JishoResultMeta { -// int id = 0; +// int id; // int status; + +// JishoResultMeta({ +// this.id = 0, +// required this.status, +// }); + +// JishoResultMeta.fromJishoObject(final jisho.JishoResultMeta object) +// : id = 0, +// status = object.status; // } // @Entity() // class JishoResult { -// int id = 0; -// final attribution = ToOne(); -// bool is_common; -// final japanese = ToMany(); +// int id; +// JishoAttribution attribution; +// bool? is_common; +// List japanese; // List jlpt; -// final senses = ToMany(); +// List senses; // String slug; // List tags; + +// JishoResult({ +// this.id = 0, +// required this.attribution, +// required this.is_common, +// required this.japanese, +// required this.jlpt, +// required this.senses, +// required this.slug, +// required this.tags, +// }); + +// JishoResult.fromJishoObject(final jisho.JishoResult object) +// : id = 0, +// attribution = JishoAttribution.fromJishoObject(object.attribution), +// is_common = object.isCommon, +// japanese = object.japanese +// .map((j) => JishoJapaneseWord.fromJishoObject(j)) +// .toList(), +// jlpt = object.jlpt, +// senses = object.senses +// .map((s) => JishoWordSense.fromJishoObject(s)) +// .toList(), +// slug = object.slug, +// tags = object.tags; // } // @Entity() // class JishoAttribution { -// int id = 0; -// String dbpedia; -// String jmdict; +// int id; +// String? dbpedia; +// bool jmdict; // bool jmnedict; + +// JishoAttribution({ +// this.id = 0, +// required this.dbpedia, +// required this.jmdict, +// required this.jmnedict, +// }); + +// JishoAttribution.fromJishoObject(final jisho.JishoAttribution object) +// : id = 0, +// dbpedia = object.dbpedia, +// jmdict = object.jmdict, +// jmnedict = object.jmnedict; // } // @Entity() // class JishoJapaneseWord { -// int id = 0; -// String reading; -// String word; +// int id; +// String? reading; +// String? word; + +// JishoJapaneseWord({ +// this.id = 0, +// required this.reading, +// required this.word, +// }); + +// JishoJapaneseWord.fromJishoObject(final jisho.JishoJapaneseWord object) +// : id = 0, +// reading = object.reading, +// word = object.word; // } // @Entity() // class JishoWordSense { -// int id = 0; +// int id; // List antonyms; // List english_definitions; // List info; -// final links = ToMany(); +// List links; // List parts_of_speech; // List restrictions; // List see_also; -// List source; +// List source; // List tags; + +// JishoWordSense({ +// this.id = 0, +// required this.antonyms, +// required this.english_definitions, +// required this.info, +// required this.links, +// required this.parts_of_speech, +// required this.restrictions, +// required this.see_also, +// required this.source, +// required this.tags, +// }); + +// JishoWordSense.fromJishoObject(final jisho.JishoWordSense object) +// : id = 0, +// antonyms = object.antonyms, +// english_definitions = object.englishDefinitions, +// info = object.info, +// links = +// object.links.map((l) => JishoSenseLink.fromJishoObject(l)).toList(), +// parts_of_speech = object.partsOfSpeech, +// restrictions = object.restrictions, +// see_also = object.seeAlso, +// source = object.source +// .map((s) => JishoWordSource.fromJishoObject(s)) +// .toList(), +// tags = object.tags; +// } + +// @Entity() +// class JishoWordSource { +// int id; +// String language; +// String? word; + +// JishoWordSource({ +// this.id = 0, +// required this.language, +// required this.word, +// }); + +// JishoWordSource.fromJishoObject(final jisho.JishoWordSource object) +// : id = 0, +// language = object.language, +// word = object.word; // } // @Entity() // class JishoSenseLink { -// int id = 0; +// int id; // String text; // String url; + +// JishoSenseLink({ +// this.id = 0, +// required this.text, +// required this.url, +// }); + +// JishoSenseLink.fromJishoObject(final jisho.JishoSenseLink object) +// : id = 0, +// text = object.text, +// url = object.url; // } diff --git a/lib/models/themes/dark.dart b/lib/models/themes/dark.dart index 618fc73..cc7a903 100644 --- a/lib/models/themes/dark.dart +++ b/lib/models/themes/dark.dart @@ -1,34 +1,42 @@ part of './theme.dart'; class DarkTheme extends AppTheme { + @override ColorSet get kanjiResultColor => const ColorSet( foreground: Colors.white, background: Colors.green, ); + @override ColorSet get onyomiColor => const ColorSet( foreground: Colors.white, background: Colors.orange, ); + @override ColorSet get kunyomiColor => const ColorSet( foreground: Colors.white, background: Colors.lightBlue, ); + @override Color get foreground => Colors.black; + @override Color get background => Colors.white; + @override ColorSet get menuGreyLight => ColorSet( foreground: Colors.white, background: Colors.grey.shade700, ); - ColorSet get menuGreyNormal => ColorSet( + @override + ColorSet get menuGreyNormal => const ColorSet( foreground: Colors.white, background: Colors.grey, ); + @override ColorSet get menuGreyDark => ColorSet( foreground: Colors.black, background: Colors.grey.shade300, diff --git a/lib/models/themes/light.dart b/lib/models/themes/light.dart index d1cccc2..6b7fcc1 100644 --- a/lib/models/themes/light.dart +++ b/lib/models/themes/light.dart @@ -1,33 +1,41 @@ part of './theme.dart'; class LightTheme extends AppTheme { + @override ColorSet get kanjiResultColor => const ColorSet( foreground: Colors.white, background: Colors.blue, ); + @override ColorSet get onyomiColor => const ColorSet( foreground: Colors.white, background: Colors.orange, ); + @override ColorSet get kunyomiColor => const ColorSet( foreground: Colors.white, background: Colors.lightBlue, ); + @override Color get foreground => Colors.black; + @override Color get background => Colors.white; + @override ColorSet get menuGreyLight => ColorSet( foreground: Colors.black, background: Colors.grey.shade300, ); - ColorSet get menuGreyNormal => ColorSet( + @override + ColorSet get menuGreyNormal => const ColorSet( foreground: Colors.white, background: Colors.grey, ); + @override ColorSet get menuGreyDark => ColorSet( foreground: Colors.white, background: Colors.grey.shade700, diff --git a/lib/models/themes/theme.dart b/lib/models/themes/theme.dart index d1a591f..705be40 100644 --- a/lib/models/themes/theme.dart +++ b/lib/models/themes/theme.dart @@ -4,7 +4,6 @@ part 'light.dart'; part 'dark.dart'; abstract class AppTheme { - static const ColorSet jishoGreen = ColorSet( foreground: Colors.white, background: Color(0xFF3EDD00), @@ -49,14 +48,17 @@ class ColorSet { /// Source: https://blog.usejournal.com/creating-a-custom-color-swatch-in-flutter-554bcdcb27f3 MaterialColor createMaterialColor(Color color) { - List strengths = [.05]; + final List strengths = [.05]; final swatch = {}; - final int r = color.red, g = color.green, b = color.blue; + final int r = color.red; + final int g = color.green; + final int b = color.blue; for (int i = 1; i < 10; i++) { strengths.add(0.1 * i); } - strengths.forEach((strength) { + + for (final strength in strengths) { final double ds = 0.5 - strength; swatch[(strength * 1000).round()] = Color.fromRGBO( r + ((ds < 0 ? r : (255 - r)) * ds).round(), @@ -64,6 +66,6 @@ MaterialColor createMaterialColor(Color color) { b + ((ds < 0 ? b : (255 - b)) * ds).round(), 1, ); - }); + } return MaterialColor(color.value, swatch); -} \ No newline at end of file +} diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json deleted file mode 100644 index 8c9f338..0000000 --- a/lib/objectbox-model.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "_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": "3:8314315977756262774", - "lastPropertyId": "4:7972948456299367594", - "name": "WordResult", - "properties": [ - { - "id": "1:8286440150679521496", - "name": "id", - "type": 6, - "flags": 1 - }, - { - "id": "2:2698026687178480112", - "name": "timestamp", - "type": 10 - }, - { - "id": "3:8750782874894963158", - "name": "word", - "type": 9 - }, - { - "id": "4:7972948456299367594", - "name": "searchStringId", - "type": 11, - "flags": 520, - "indexId": "1:6146948198859733323", - "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": "6:8118874861016646859", - "lastIndexId": "5:5394995618034342416", - "lastRelationId": "1:2624712325077938293", - "lastSequenceId": "0:0", - "modelVersion": 5, - "modelVersionParserMinimum": 5, - "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/router.dart b/lib/router.dart new file mode 100644 index 0000000..5b9b5ce --- /dev/null +++ b/lib/router.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +import 'view/home.dart'; +import 'view/screens/search/kanji_result_page.dart'; +import 'view/screens/search/search_results_page.dart'; + +Route generateRoute(RouteSettings settings) { + final args = settings.arguments; + + switch (settings.name) { + case '/': + return MaterialPageRoute(builder: (_) => const Home()); + + case '/search': + final searchTerm = args! as String; + return MaterialPageRoute( + builder: (_) => SearchResultsPage(searchTerm: searchTerm), + ); + + case '/kanjiSearch': + final searchTerm = args! as String; + return MaterialPageRoute( + builder: (_) => KanjiResultPage(kanjiSearchTerm: searchTerm), + ); + + default: + return MaterialPageRoute( + builder: (_) => const Text('ERROR: this route does not exist'), + ); + } +} diff --git a/lib/services/jisho_api/jisho_search.dart b/lib/services/jisho_api/jisho_search.dart index eb1e379..3459bda 100644 --- a/lib/services/jisho_api/jisho_search.dart +++ b/lib/services/jisho_api/jisho_search.dart @@ -1,5 +1,6 @@ import 'package:unofficial_jisho_api/api.dart' as jisho; +export 'package:unofficial_jisho_api/api.dart' show JishoAPIResult; Future fetchJishoResults(searchTerm) async { - return await jisho.searchForPhrase(searchTerm); -} \ No newline at end of file + return jisho.searchForPhrase(searchTerm); +} diff --git a/lib/services/jisho_api/kanji_search.dart b/lib/services/jisho_api/kanji_search.dart index 2c55d61..76e8a31 100644 --- a/lib/services/jisho_api/kanji_search.dart +++ b/lib/services/jisho_api/kanji_search.dart @@ -1,17 +1,19 @@ +import 'package:flutter/material.dart'; import 'package:unofficial_jisho_api/api.dart' as jisho; +export 'package:unofficial_jisho_api/api.dart' show KanjiResult; String? _convertGrade(String grade) { const conversionTable = { - "grade 1": "小1", - "grade 2": "小2", - "grade 3": "小3", - "grade 4": "小4", - "grade 5": "小5", - "grade 6": "小6", - "junior high": "中" + 'grade 1': '小1', + 'grade 2': '小2', + 'grade 3': '小3', + 'grade 4': '小4', + 'grade 5': '小5', + 'grade 6': '小6', + 'junior high': '中' }; - print('conversion run: $grade -> ${conversionTable[grade]}'); + debugPrint('conversion run: $grade -> ${conversionTable[grade]}'); return conversionTable[grade]; } @@ -23,4 +25,4 @@ Future fetchKanji(String kanji) async { if (result.data != null && result.data?.taughtIn != null) result.data!.taughtIn = _convertGrade(result.data!.taughtIn!); return result; -} \ No newline at end of file +} diff --git a/lib/view/components/search/search_result_page/parts/wikipedia_attribute.dart b/lib/view/components/common/internet_error.dart similarity index 100% rename from lib/view/components/search/search_result_page/parts/wikipedia_attribute.dart rename to lib/view/components/common/internet_error.dart diff --git a/lib/view/screens/loading.dart b/lib/view/components/common/loading.dart similarity index 55% rename from lib/view/screens/loading.dart rename to lib/view/components/common/loading.dart index 327c44c..2f30fff 100644 --- a/lib/view/screens/loading.dart +++ b/lib/view/components/common/loading.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; class LoadingScreen extends StatelessWidget { + const LoadingScreen({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return Container( - child: Center( - child: CircularProgressIndicator(), - ), + return const Center( + child: CircularProgressIndicator(), ); } } diff --git a/lib/view/screens/splash.dart b/lib/view/components/common/splash.dart similarity index 62% rename from lib/view/screens/splash.dart rename to lib/view/components/common/splash.dart index 350d7d2..e453f34 100644 --- a/lib/view/screens/splash.dart +++ b/lib/view/components/common/splash.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; + +import '../../../models/themes/theme.dart'; class SplashScreen extends StatelessWidget { const SplashScreen({Key? key}) : super(key: key); @@ -8,9 +9,11 @@ class SplashScreen extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration(color: AppTheme.jishoGreen.background), - child: Center( - child: Image.asset('assets/images/logo/logo_icon_transparent.png'), + child: const Center( + child: Image( + image: AssetImage('assets/images/logo/logo_icon_transparent.png'), + ), ), ); } -} \ No newline at end of file +} diff --git a/lib/view/screens/kanji/search/drawing.dart b/lib/view/components/common/unknown_error.dart similarity index 100% rename from lib/view/screens/kanji/search/drawing.dart rename to lib/view/components/common/unknown_error.dart diff --git a/lib/view/components/history/date_divider.dart b/lib/view/components/history/date_divider.dart index 992728e..6d98fc1 100644 --- a/lib/view/components/history/date_divider.dart +++ b/lib/view/components/history/date_divider.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; + +import '../../../bloc/theme/theme_bloc.dart'; +import '../../../models/themes/theme.dart'; class DateDivider extends StatelessWidget { final String? text; @@ -33,31 +34,31 @@ class DateDivider extends StatelessWidget { final int day = date.day; final String month = monthTable[date.month]!; final int year = date.year; - return "$day. $month $year"; + return '$day. $month $year'; } @override Widget build(BuildContext context) { - final Widget header = (this.text != null) - ? Text(this.text!) - : (this.date != null) - ? Text(getHumanReadableDate(this.date!)) - : SizedBox.shrink(); + final Widget header = (text != null) + ? Text(text!) + : (date != null) + ? Text(getHumanReadableDate(date!)) + : const SizedBox.shrink(); final ColorSet _menuColors = BlocProvider.of(context).state.theme.menuGreyNormal; return Container( + decoration: BoxDecoration(color: _menuColors.background), + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), + margin: margin, child: DefaultTextStyle.merge( child: header, style: TextStyle(color: _menuColors.foreground), ), - decoration: BoxDecoration(color: _menuColors.background), - padding: EdgeInsets.symmetric( - vertical: 5, - horizontal: 10, - ), - margin: this.margin, ); } } diff --git a/lib/view/components/history/kanji_search_item.dart b/lib/view/components/history/kanji_search_item.dart index eb7be2f..15ce4c5 100644 --- a/lib/view/components/history/kanji_search_item.dart +++ b/lib/view/components/history/kanji_search_item.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.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/bloc/theme/theme_bloc.dart'; -import 'package:jisho_study_tool/models/history/kanji_query.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; import './search_item.dart'; +import '../../../bloc/theme/theme_bloc.dart'; +import '../../../models/history/kanji_query.dart'; +import '../../../models/themes/theme.dart'; class _KanjiBox extends StatelessWidget { final String kanji; @@ -15,13 +13,14 @@ class _KanjiBox extends StatelessWidget { @override Widget build(BuildContext context) { - final ColorSet _menuColors = BlocProvider.of(context).state.theme.menuGreyLight; + final ColorSet _menuColors = + BlocProvider.of(context).state.theme.menuGreyLight; return IntrinsicHeight( child: AspectRatio( aspectRatio: 1, child: Container( - padding: EdgeInsets.all(5), + padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: _menuColors.background, borderRadius: BorderRadius.circular(10.0), @@ -56,27 +55,30 @@ class KanjiSearchItem extends StatelessWidget { @override Widget build(BuildContext context) { return Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + label: 'Favourite', + backgroundColor: Colors.yellow, + icon: Icons.star, + onPressed: (_) {}, + ), + SlidableAction( + label: 'Delete', + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (_) {}, + ), + ], + ), child: SearchItem( onTap: () { - BlocProvider.of(context).add(ChangePage(1)); - BlocProvider.of(context).add(GetKanji(this.result.kanji)); + Navigator.pushNamed(context, '/kanjiSearch', arguments: result.kanji); }, time: timestamp, search: _KanjiBox(result.kanji), ), - actionPane: SlidableScrollActionPane(), - secondaryActions: [ - IconSlideAction( - caption: "Favourite", - color: Colors.yellow, - icon: Icons.star, - ), - IconSlideAction( - caption: "Delete", - color: Colors.red, - icon: Icons.delete, - ), - ], ); } } diff --git a/lib/view/components/history/phrase_search_item.dart b/lib/view/components/history/phrase_search_item.dart index 30273bd..d6d4377 100644 --- a/lib/view/components/history/phrase_search_item.dart +++ b/lib/view/components/history/phrase_search_item.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.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'; +import '../../../models/history/word_query.dart'; class PhraseSearchItem extends StatelessWidget { final WordQuery search; @@ -19,16 +17,25 @@ class PhraseSearchItem extends StatelessWidget { @override Widget build(BuildContext context) { return Slidable( - actionPane: SlidableScrollActionPane(), - secondaryActions: [ - IconSlideAction( - caption: "Delete", color: Colors.red, icon: Icons.delete) - ], + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + + SlidableAction( + label: 'Delete', + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (_) {}, + ), + ], + + ), child: SearchItem( - onTap: () { - BlocProvider.of(context).add(ChangePage(0)); - BlocProvider.of(context).add(GetSearchResults(this.search.query)); - }, + onTap: () => Navigator.pushNamed( + context, + '/search', + arguments: 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 826a4fa..7025b18 100644 --- a/lib/view/components/history/search_item.dart +++ b/lib/view/components/history/search_item.dart @@ -13,26 +13,24 @@ class SearchItem extends StatelessWidget { }) : 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"; + final hours = time.hour.toString().padLeft(2, '0'); + final mins = time.minute.toString().padLeft(2, '0'); + return '$hours:$mins'; } @override Widget build(BuildContext context) { - return Container( - child: ListTile( - onTap: onTap, - contentPadding: EdgeInsets.zero, - title: Row( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 20), - child: Text(getTime()), - ), - search, - ], - ), + return ListTile( + onTap: onTap, + contentPadding: EdgeInsets.zero, + title: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(getTime()), + ), + search, + ], ), ); } diff --git a/lib/view/components/kanji/kanji_result_body.dart b/lib/view/components/kanji/kanji_result_body.dart new file mode 100644 index 0000000..f94c128 --- /dev/null +++ b/lib/view/components/kanji/kanji_result_body.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:unofficial_jisho_api/api.dart' as jisho; + +import './kanji_result_body/examples.dart'; +import './kanji_result_body/grade.dart'; +import './kanji_result_body/header.dart'; +import './kanji_result_body/jlpt_level.dart'; +import './kanji_result_body/radical.dart'; +import './kanji_result_body/rank.dart'; +import './kanji_result_body/stroke_order_gif.dart'; +import './kanji_result_body/yomi_chips.dart'; + +class KanjiResultBody extends StatelessWidget { + late final String query; + late final jisho.KanjiResultData resultData; + + KanjiResultBody({required jisho.KanjiResult result, Key? key}) + : super(key: key) { + query = result.query; + + // TODO: Handle this kind of exception before widget is initialized + if (result.data == null) throw Exception(); + + resultData = result.data!; + } + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + Container( + margin: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Flexible( + fit: FlexFit.tight, + child: Center(child: SizedBox()), + ), + Flexible( + fit: FlexFit.tight, + child: Center(child: Header(kanji: query)), + ), + Flexible( + fit: FlexFit.tight, + child: Center( + child: (resultData.radical != null) + ? Radical(radical: resultData.radical!) + : const SizedBox(), + ), + ), + ], + ), + ), + YomiChips(yomi: resultData.meaning.split(', '), type: YomiType.meaning), + (resultData.onyomi.isNotEmpty) + ? YomiChips(yomi: resultData.onyomi, type: YomiType.onyomi) + : const SizedBox.shrink(), + (resultData.kunyomi.isNotEmpty) + ? YomiChips(yomi: resultData.kunyomi, type: YomiType.kunyomi) + : const SizedBox.shrink(), + IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + StrokeOrderGif(uri: resultData.strokeOrderGifUri), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('JLPT: ', style: TextStyle(fontSize: 20.0)), + JlptLevel(jlptLevel: resultData.jlptLevel ?? '⨉'), + ], + ), + Row( + children: [ + const Text('Grade: ', style: TextStyle(fontSize: 20.0)), + Grade(grade: resultData.taughtIn ?? '⨉'), + ], + ), + Row( + children: [ + const Text('Rank: ', style: TextStyle(fontSize: 20.0)), + Rank(rank: resultData.newspaperFrequencyRank ?? -1), + ], + ), + ], + ), + ], + ), + ), + Examples( + onyomi: resultData.onyomiExamples, + kunyomi: resultData.kunyomiExamples, + ), + ], + ); + } +} diff --git a/lib/view/components/kanji/result/examples.dart b/lib/view/components/kanji/kanji_result_body/examples.dart similarity index 54% rename from lib/view/components/kanji/result/examples.dart rename to lib/view/components/kanji/kanji_result_body/examples.dart index 9f4b78e..8740336 100644 --- a/lib/view/components/kanji/result/examples.dart +++ b/lib/view/components/kanji/kanji_result_body/examples.dart @@ -1,15 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; import 'package:unofficial_jisho_api/api.dart'; -class Examples extends StatelessWidget { - final List onyomiExamples; - final List kunyomiExamples; +import '../../../../bloc/theme/theme_bloc.dart'; - const Examples( - this.onyomiExamples, - this.kunyomiExamples, - ); +class Examples extends StatelessWidget { + final List onyomi; + final List kunyomi; + + const Examples({ + required this.onyomi, + required this.kunyomi, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -17,20 +19,21 @@ class Examples extends StatelessWidget { children: [ [ Container( - margin: EdgeInsets.symmetric(horizontal: 10), + margin: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.centerLeft, - child: Text( + child: const Text( 'Examples:', style: TextStyle(fontSize: 20), ), ) ], - onyomiExamples + onyomi .map((onyomiExample) => _Example(onyomiExample, _KanaType.onyomi)) .toList(), - kunyomiExamples + kunyomi .map( - (kunyomiExample) => _Example(kunyomiExample, _KanaType.kunyomi)) + (kunyomiExample) => _Example(kunyomiExample, _KanaType.kunyomi), + ) .toList(), ].expand((list) => list).toList(), ); @@ -48,68 +51,66 @@ class _Example extends StatelessWidget { @override Widget build(BuildContext context) { final _themeData = BlocProvider.of(context).state.theme; - final _kanaColors = kanaType == _KanaType.kunyomi ? _themeData.kunyomiColor : _themeData.onyomiColor; + final _kanaColors = kanaType == _KanaType.kunyomi + ? _themeData.kunyomiColor + : _themeData.onyomiColor; final _menuColors = _themeData.menuGreyNormal; - return Container( - margin: EdgeInsets.symmetric( + return Container( + margin: const EdgeInsets.symmetric( vertical: 5.0, horizontal: 10.0, ), decoration: BoxDecoration( - color: _menuColors.background, borderRadius: BorderRadius.circular(10.0)), + color: _menuColors.background, + borderRadius: BorderRadius.circular(10.0), + ), child: Row( children: [ Container( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 10.0, ), decoration: BoxDecoration( color: _kanaColors.background, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0), ), ), child: Column( children: [ - Container( - child: Text( - yomiExample.reading, - style: TextStyle( - color: _kanaColors.foreground, - fontSize: 15.0, - ), + Text( + yomiExample.reading, + style: TextStyle( + color: _kanaColors.foreground, + fontSize: 15.0, ), ), - SizedBox( + const SizedBox( height: 5.0, ), - Container( - child: Text( - yomiExample.example, - style: TextStyle( - color: _kanaColors.foreground, - fontSize: 20.0, - ), + Text( + yomiExample.example, + style: TextStyle( + color: _kanaColors.foreground, + fontSize: 20.0, ), ), ], ), ), - SizedBox( + const SizedBox( width: 15.0, ), Expanded( child: Wrap( children: [ - Container( - child: Text( - yomiExample.meaning, - style: TextStyle( - color: _menuColors.foreground, - ), + Text( + yomiExample.meaning, + style: TextStyle( + color: _menuColors.foreground, ), ) ], diff --git a/lib/view/components/kanji/result/grade.dart b/lib/view/components/kanji/kanji_result_body/grade.dart similarity index 77% rename from lib/view/components/kanji/result/grade.dart rename to lib/view/components/kanji/kanji_result_body/grade.dart index 12fb36e..abe2443 100644 --- a/lib/view/components/kanji/result/grade.dart +++ b/lib/view/components/kanji/kanji_result_body/grade.dart @@ -1,17 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; class Grade extends StatelessWidget { final String grade; - const Grade(this.grade); + const Grade({required this.grade, Key? key}) : super(key: key); @override Widget build(BuildContext context) { final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: _kanjiColors.background, + shape: BoxShape.circle, + ), child: Text( grade, style: TextStyle( @@ -19,10 +24,6 @@ class Grade extends StatelessWidget { fontSize: 20.0, ), ), - decoration: BoxDecoration( - color: _kanjiColors.background, - shape: BoxShape.circle, - ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/result/header.dart b/lib/view/components/kanji/kanji_result_body/header.dart similarity index 70% rename from lib/view/components/kanji/result/header.dart rename to lib/view/components/kanji/kanji_result_body/header.dart index d0f999c..c86fb5d 100644 --- a/lib/view/components/kanji/result/header.dart +++ b/lib/view/components/kanji/kanji_result_body/header.dart @@ -1,14 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; class Header extends StatelessWidget { final String kanji; - const Header(this.kanji); + const Header({ + required this.kanji, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; + final _kanjiColors = + BlocProvider.of(context).state.theme.kanjiResultColor; return AspectRatio( aspectRatio: 1, diff --git a/lib/view/components/kanji/result/jlpt_level.dart b/lib/view/components/kanji/kanji_result_body/jlpt_level.dart similarity index 62% rename from lib/view/components/kanji/result/jlpt_level.dart rename to lib/view/components/kanji/kanji_result_body/jlpt_level.dart index 2cc2fe1..f63cbc3 100644 --- a/lib/view/components/kanji/result/jlpt_level.dart +++ b/lib/view/components/kanji/kanji_result_body/jlpt_level.dart @@ -1,18 +1,26 @@ - import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; class JlptLevel extends StatelessWidget { final String jlptLevel; - const JlptLevel(this.jlptLevel); + const JlptLevel({ + required this.jlptLevel, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { - final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; + final _kanjiColors = + BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _kanjiColors.background, + ), child: Text( jlptLevel, style: TextStyle( @@ -20,10 +28,6 @@ class JlptLevel extends StatelessWidget { fontSize: 20.0, ), ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _kanjiColors.background, - ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/result/radical.dart b/lib/view/components/kanji/kanji_result_body/radical.dart similarity index 78% rename from lib/view/components/kanji/result/radical.dart rename to lib/view/components/kanji/kanji_result_body/radical.dart index d208e00..aa7ef1e 100644 --- a/lib/view/components/kanji/result/radical.dart +++ b/lib/view/components/kanji/kanji_result_body/radical.dart @@ -1,18 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; import 'package:unofficial_jisho_api/api.dart' as jisho; +import '../../../../bloc/theme/theme_bloc.dart'; + class Radical extends StatelessWidget { final jisho.Radical radical; - const Radical(this.radical); + const Radical({required this.radical, Key? key,}) : super(key: key); + @override Widget build(BuildContext context) { final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _kanjiColors.background, + ), child: Text( radical.symbol, style: TextStyle( @@ -20,10 +26,6 @@ class Radical extends StatelessWidget { fontSize: 40.0, ), ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _kanjiColors.background, - ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/result/rank.dart b/lib/view/components/kanji/kanji_result_body/rank.dart similarity index 78% rename from lib/view/components/kanji/result/rank.dart rename to lib/view/components/kanji/kanji_result_body/rank.dart index 24356fb..89279ac 100644 --- a/lib/view/components/kanji/result/rank.dart +++ b/lib/view/components/kanji/kanji_result_body/rank.dart @@ -1,18 +1,23 @@ - import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; class Rank extends StatelessWidget { final int rank; - const Rank(this.rank); + const Rank({required this.rank, Key? key,}) : super(key: key); + @override Widget build(BuildContext context) { final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: _kanjiColors.background, + ), child: Text( '${rank.toString()} / 2500', style: TextStyle( @@ -20,10 +25,6 @@ class Rank extends StatelessWidget { fontSize: 20.0, ), ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - color: _kanjiColors.background, - ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/result/stroke_order_gif.dart b/lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart similarity index 70% rename from lib/view/components/kanji/result/stroke_order_gif.dart rename to lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart index ecda698..75fb00d 100644 --- a/lib/view/components/kanji/result/stroke_order_gif.dart +++ b/lib/view/components/kanji/kanji_result_body/stroke_order_gif.dart @@ -1,26 +1,28 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; class StrokeOrderGif extends StatelessWidget { final String uri; - const StrokeOrderGif(this.uri); + const StrokeOrderGif({required this.uri, Key? key,}) : super(key: key); + @override Widget build(BuildContext context) { final _kanjiColors = BlocProvider.of(context).state.theme.kanjiResultColor; return Container( - margin: EdgeInsets.symmetric(vertical: 20.0), - padding: EdgeInsets.all(5.0), - child: ClipRRect( - child: Image.network(uri), - borderRadius: BorderRadius.circular(10.0), - ), + margin: const EdgeInsets.symmetric(vertical: 20.0), + padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: _kanjiColors.background, borderRadius: BorderRadius.circular(15.0), ), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Image.network(uri), + ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/kanji_result_body/yomi_chips.dart b/lib/view/components/kanji/kanji_result_body/yomi_chips.dart new file mode 100644 index 0000000..ce5eb20 --- /dev/null +++ b/lib/view/components/kanji/kanji_result_body/yomi_chips.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; + +import '../../../../bloc/theme/theme_bloc.dart'; + +enum YomiType { + onyomi, + kunyomi, + meaning, +} + +extension on YomiType { + String get title { + switch (this) { + case YomiType.onyomi: + return 'Onyomi'; + case YomiType.kunyomi: + return 'Kunyomi'; + case YomiType.meaning: + return 'Meanings'; + } + } + + ColorSet getColors(BuildContext context) { + final theme = BlocProvider.of(context).state.theme; + + switch (this) { + case YomiType.onyomi: + return theme.onyomiColor; + case YomiType.kunyomi: + return theme.kunyomiColor; + case YomiType.meaning: + return theme.menuGreyNormal; + } + } +} + +class YomiChips extends StatelessWidget { + final List yomi; + final YomiType type; + + const YomiChips({ + required this.yomi, + required this.type, + Key? key, + }) : super(key: key); + + bool get isExpandable => yomi.length > 6; + + Widget yomiCard({ + required BuildContext context, + required String yomi, + required ColorSet colors, + }) => + Container( + margin: const EdgeInsets.symmetric(horizontal: 10.0), + padding: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + decoration: BoxDecoration( + color: type.getColors(context).background, + borderRadius: BorderRadius.circular(10.0), + ), + child: Text( + yomi, + style: TextStyle( + fontSize: 20.0, + color: colors.foreground, + ), + ), + ); + + Widget yomiWrapper(BuildContext context) { + final yomiCards = yomi + .map( + (y) => yomiCard( + context: context, + yomi: y, + colors: type.getColors(context), + ), + ) + .toList(); + + if (!isExpandable) + return Wrap( + runSpacing: 10.0, + children: yomiCards, + ); + else + return ExpansionTile( + // initiallyExpanded: false, + title: Center( + child: yomiCard( + context: context, + yomi: type.title, + colors: type.getColors(context), + ), + ), + children: [ + const SizedBox( + height: 20.0, + ), + Wrap( + runSpacing: 10.0, + children: yomiCards, + ), + const SizedBox( + height: 25.0, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0, + ), + alignment: Alignment.centerLeft, + child: yomiWrapper(context), + ); + } +} diff --git a/lib/view/components/kanji/kanji_search_bar.dart b/lib/view/components/kanji/kanji_search_bar.dart deleted file mode 100644 index fba6409..0000000 --- a/lib/view/components/kanji/kanji_search_bar.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart'; - -class KanjiSearchBar extends StatefulWidget { - - const KanjiSearchBar(); - - @override - _KanjiSearchBarState createState() => new _KanjiSearchBarState(); -} - -enum TextFieldButton {clear, paste} - -class _KanjiSearchBarState extends State { - final TextEditingController textController = new TextEditingController(); - TextFieldButton button = TextFieldButton.paste; - - @override - void initState() { - super.initState(); - } - - void _getKanjiSuggestions(String text) => - BlocProvider.of(context).add(GetKanjiSuggestions(text)); - - void updateSuggestions() => _getKanjiSuggestions(textController.text); - - void _clearText() { - textController.text = ''; - updateSuggestions(); - } - - void _pasteText() async { - ClipboardData? clipboardData = await Clipboard.getData('text/plain'); - if (clipboardData != null && clipboardData.text != null) { - textController.text = clipboardData.text!; - updateSuggestions(); - } - } - - @override - Widget build(BuildContext context) { - IconButton clearButton = IconButton( - icon: Icon(Icons.clear), - onPressed: () => _clearText(), - ); - - IconButton pasteButton = IconButton( - icon: Icon(Icons.content_paste), - onPressed: () => _pasteText(), - ); - - return TextField( - controller: textController, - onChanged: (text) => _getKanjiSuggestions(text), - onSubmitted: (_) => {}, - decoration: new InputDecoration( - prefixIcon: Icon(Icons.search), - hintText: 'Search', - // fillColor: Colors.white, - // filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - ), - isDense: false, - suffixIcon: (button == TextFieldButton.clear) ? clearButton : pasteButton, - ), - ); - } -} \ No newline at end of file diff --git a/lib/view/components/kanji/kanji_search_body.dart b/lib/view/components/kanji/kanji_search_body.dart new file mode 100644 index 0000000..3ba2e76 --- /dev/null +++ b/lib/view/components/kanji/kanji_search_body.dart @@ -0,0 +1,108 @@ +import 'package:animated_size_and_fade/animated_size_and_fade.dart'; +import 'package:flutter/material.dart'; + +import '../../../services/kanji_suggestions.dart'; +import 'kanji_search_body/kanji_grid.dart'; +import 'kanji_search_body/kanji_search_bar.dart'; +import 'kanji_search_body/kanji_search_options_bar.dart'; + +class KanjiSearchBody extends StatefulWidget { + const KanjiSearchBody({Key? key}) : super(key: key); + + @override + _KanjiSearchBodyState createState() => _KanjiSearchBodyState(); +} + +class _KanjiSearchBodyState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _searchbarMovementAnimation; + final FocusNode focus = FocusNode(); + final GlobalKey _kanjiSearchBarState = + GlobalKey(); + List suggestions = []; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + + _searchbarMovementAnimation = AlignmentTween( + begin: Alignment.center, + end: Alignment.topCenter, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (_controller.value == 1) { + focus.unfocus(); + _kanjiSearchBarState.currentState!.clearText(); + return false; + } + return true; + }, + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: const BoxDecoration(), + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: AnimatedBuilder( + animation: _searchbarMovementAnimation, + builder: (context, _) { + return Container( + alignment: _searchbarMovementAnimation.value, + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Focus( + focusNode: focus, + onFocusChange: (hasFocus) { + if (hasFocus) + _controller.forward(); + else + _controller.reverse(); + }, + child: KanjiSearchBar( + key: _kanjiSearchBarState, + onChanged: (text) => setState(() { + suggestions = kanjiSuggestions(text); + }), + ), + ), + AnimatedSizeAndFade( + fadeDuration: const Duration(milliseconds: 200), + sizeDuration: const Duration(milliseconds: 300), + child: _controller.value == 1 + ? KanjiGrid(suggestions: suggestions) + : const KanjiSearchOptionsBar(), + ), + ], + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/view/components/kanji/kanji_grid.dart b/lib/view/components/kanji/kanji_search_body/kanji_grid.dart similarity index 55% rename from lib/view/components/kanji/kanji_grid.dart rename to lib/view/components/kanji/kanji_search_body/kanji_grid.dart index 5ea3802..8373be9 100644 --- a/lib/view/components/kanji/kanji_grid.dart +++ b/lib/view/components/kanji/kanji_search_body/kanji_grid.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; +import '../../../../bloc/theme/theme_bloc.dart'; class KanjiGrid extends StatelessWidget { final List suggestions; - const KanjiGrid(this.suggestions); + + const KanjiGrid({required this.suggestions, Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( vertical: 20.0, horizontal: 40.0, ), @@ -33,29 +33,28 @@ class _GridItem extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: () { - BlocProvider.of(context).add(GetKanji(kanji)); + Navigator.pushNamed(context, '/kanjiSearch', arguments: kanji); }, child: BlocBuilder( builder: (context, state) { final _menuColors = state.theme.menuGreyLight; - return - Container( - decoration: BoxDecoration( - color: _menuColors.background, - borderRadius: BorderRadius.circular(20.0), - ), - child: Container( - margin: EdgeInsets.all(10.0), - child: FittedBox( - child: Text( - kanji, - style: TextStyle(color: _menuColors.foreground), + return Container( + decoration: BoxDecoration( + color: _menuColors.background, + borderRadius: BorderRadius.circular(20.0), ), - ), - ), - ); + child: Container( + margin: const EdgeInsets.all(10.0), + child: FittedBox( + child: Text( + kanji, + style: TextStyle(color: _menuColors.foreground), + ), + ), + ), + ); }, ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart b/lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart new file mode 100644 index 0000000..ce1b806 --- /dev/null +++ b/lib/view/components/kanji/kanji_search_body/kanji_search_bar.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class KanjiSearchBar extends StatefulWidget { + final Function(String)? onChanged; + + const KanjiSearchBar({this.onChanged, Key? key}) : super(key: key); + + @override + KanjiSearchBarState createState() => KanjiSearchBarState(); +} + +enum TextFieldButton { clear, paste } + +class KanjiSearchBarState extends State { + final TextEditingController textController = TextEditingController(); + TextFieldButton button = TextFieldButton.paste; + + @override + void initState() { + super.initState(); + } + + void runOnChanged() { + if (widget.onChanged != null) widget.onChanged!(textController.text); + } + + void clearText() { + textController.text = ''; + runOnChanged(); + } + + Future pasteText() async { + final ClipboardData? clipboardData = await Clipboard.getData('text/plain'); + if (clipboardData != null && clipboardData.text != null) { + textController.text = clipboardData.text!; + runOnChanged(); + } + } + + @override + Widget build(BuildContext context) { + final IconButton clearButton = IconButton( + icon: const Icon(Icons.clear), + onPressed: () => clearText(), + ); + + final IconButton pasteButton = IconButton( + icon: const Icon(Icons.content_paste), + onPressed: () => pasteText(), + ); + + return TextField( + controller: textController, + onChanged: (text) { + if (widget.onChanged != null) widget.onChanged!(text); + }, + onSubmitted: (_) => {}, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + hintText: 'Search', + // fillColor: Colors.white, + // filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + isDense: false, + suffixIcon: + (button == TextFieldButton.clear) ? clearButton : pasteButton, + ), + ); + } +} diff --git a/lib/view/components/kanji/kanji_search_options_bar.dart b/lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart similarity index 65% rename from lib/view/components/kanji/kanji_search_options_bar.dart rename to lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart index 6c2085a..23aad34 100644 --- a/lib/view/components/kanji/kanji_search_options_bar.dart +++ b/lib/view/components/kanji/kanji_search_body/kanji_search_options_bar.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart'; //TODO: Make buttons have an effect @@ -13,25 +12,22 @@ class KanjiSearchOptionsBar extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ _IconButton( - icon: Text( - "部", + icon: const Text( + '部', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 18, ), ), - onPressed: () => - BlocProvider.of(context).add(ReturnToInitialState()), + onPressed: () {}, ), _IconButton( - icon: Icon(Icons.category), - onPressed: () => - BlocProvider.of(context).add(ReturnToInitialState()), + icon: const Icon(Icons.category), + onPressed: () {}, ), _IconButton( - icon: Icon(Icons.mode), - onPressed: () => - BlocProvider.of(context).add(ReturnToInitialState()), + icon: const Icon(Icons.mode), + onPressed: () {}, ), ], ), diff --git a/lib/view/components/kanji/result/yomi_chips.dart b/lib/view/components/kanji/result/yomi_chips.dart deleted file mode 100644 index 7ee1757..0000000 --- a/lib/view/components/kanji/result/yomi_chips.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; - -enum YomiType { - onyomi, - kunyomi, - meaning, -} - -extension on YomiType { - String get title { - switch (this) { - case YomiType.onyomi: - return 'Onyomi'; - case YomiType.kunyomi: - return 'Kunyomi'; - case YomiType.meaning: - return 'Meanings'; - } - } - - ColorSet getColors(BuildContext context) { - final theme = BlocProvider.of(context).state.theme; - - switch (this) { - case YomiType.onyomi: - return theme.onyomiColor; - case YomiType.kunyomi: - return theme.kunyomiColor; - case YomiType.meaning: - return theme.menuGreyNormal; - } - } -} - -class YomiChips extends StatelessWidget { - final List yomi; - final YomiType type; - - const YomiChips(this.yomi, this.type); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric( - horizontal: 10.0, - vertical: 5.0, - ), - alignment: Alignment.centerLeft, - child: _yomiWrapper(context), - ); - } - - bool isExpandable() => yomi.length > 6; - - Widget _yomiWrapper(BuildContext context) { - final yomiCards = this - .yomi - .map((yomi) => _YomiCard( - yomi: yomi, - colors: this.type.getColors(context), - )) - .toList(); - - if (!this.isExpandable()) - return Wrap( - runSpacing: 10.0, - children: yomiCards, - ); - - return ExpansionTile( - initiallyExpanded: false, - title: Center( - child: _YomiCard( - yomi: this.type.title, - colors: this.type.getColors(context), - ), - ), - children: [ - SizedBox( - height: 20.0, - ), - Wrap( - runSpacing: 10.0, - children: yomiCards, - ), - SizedBox( - height: 25.0, - ), - ], - ); - } -} - -class _YomiCard extends StatelessWidget { - final String yomi; - final ColorSet colors; - - const _YomiCard({required this.yomi, required this.colors}); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 10.0), - padding: EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 10.0, - ), - child: Text( - this.yomi, - style: TextStyle( - fontSize: 20.0, - color: colors.foreground, - ), - ), - decoration: BoxDecoration( - color: colors.background, - borderRadius: BorderRadius.circular(10.0), - ), - ); - } -} diff --git a/lib/view/components/opaque_box.dart b/lib/view/components/opaque_box.dart index c8cc7db..87d5ff5 100644 --- a/lib/view/components/opaque_box.dart +++ b/lib/view/components/opaque_box.dart @@ -9,7 +9,7 @@ class OpaqueBox extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration(color: Theme.of(context).scaffoldBackgroundColor), - child: this.child, + child: child, ); } -} \ No newline at end of file +} diff --git a/lib/view/components/search/language_selector.dart b/lib/view/components/search/language_selector.dart index b28cbe1..66255e6 100644 --- a/lib/view/components/search/language_selector.dart +++ b/lib/view/components/search/language_selector.dart @@ -1,59 +1,55 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; +import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../../models/themes/theme.dart'; + class LanguageSelector extends StatefulWidget { - const LanguageSelector(); + const LanguageSelector({Key? key}) : super(key: key); @override - _LanguageSelectorState createState() => new _LanguageSelectorState(); + _LanguageSelectorState createState() => _LanguageSelectorState(); } class _LanguageSelectorState extends State { - late final SharedPreferences prefs; + final SharedPreferences prefs = GetIt.instance.get(); late List isSelected; @override void initState() { super.initState(); - isSelected = [false, false, false]; - - SharedPreferences.getInstance() - .then((prefs) { - this.prefs = prefs; - setState(() { - isSelected = _getSelectedStatus() ?? isSelected; - }); - }); + isSelected = _getSelectedStatus() ?? [false, false, false]; } - void _updateSelectedStatus() async { - await prefs.setStringList('languageSelectorStatus', - isSelected - .map((b) => b ? '1' : '0') - .toList()); - } + Future _updateSelectedStatus() async => prefs.setStringList( + 'languageSelectorStatus', + isSelected.map((b) => b ? '1' : '0').toList(), + ); - List? _getSelectedStatus() { - return prefs + List? _getSelectedStatus() => prefs .getStringList('languageSelectorStatus') ?.map((s) => s == '1') .toList(); - } + + Widget _languageOption(String language) => + Container( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), + child: Center(child: Text(language)), + ); @override Widget build(BuildContext context) { return ToggleButtons( selectedColor: AppTheme.jishoGreen.background, isSelected: isSelected, - children: [ - _LanguageOption("Auto"), - _LanguageOption("日本語"), - _LanguageOption("English") + children: [ + _languageOption('Auto'), + _languageOption('日本語'), + _languageOption('English') ], - onPressed: (int buttonIndex) { + onPressed: (buttonIndex) { setState(() { - for (var i in Iterable.generate(isSelected.length)) { + for (final int i in Iterable.generate(isSelected.length)) { isSelected[i] = i == buttonIndex; } _updateSelectedStatus(); @@ -61,19 +57,4 @@ class _LanguageSelectorState extends State { }, ); } - } - -class _LanguageOption extends StatelessWidget { - final String language; - - const _LanguageOption(this.language); - - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), - child: Center(child: Text(language)), - ); - } -} \ No newline at end of file diff --git a/lib/view/components/search/search_bar.dart b/lib/view/components/search/search_bar.dart index b8d63d2..1a56acf 100644 --- a/lib/view/components/search/search_bar.dart +++ b/lib/view/components/search/search_bar.dart @@ -1,22 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/search/search_bloc.dart'; -import 'package:jisho_study_tool/view/components/search/language_selector.dart'; + +import 'language_selector.dart'; class SearchBar extends StatelessWidget { - - const SearchBar(); + const SearchBar({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.symmetric(horizontal: 20.0), + padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Column( children: [ TextField( - onSubmitted: (text) { - BlocProvider.of(context) - .add(GetSearchResults(text)); - }, + onSubmitted: (text) => + Navigator.pushNamed(context, '/search', arguments: text), controller: TextEditingController(), decoration: InputDecoration( labelText: 'Search', @@ -25,12 +22,10 @@ class SearchBar extends StatelessWidget { ), ), ), - SizedBox( - height: 10.0, - ), - LanguageSelector() + const SizedBox(height: 10.0), + const LanguageSelector() ], ), ); } -} \ No newline at end of file +} diff --git a/lib/view/components/search/search_result_body.dart b/lib/view/components/search/search_result_body.dart new file mode 100644 index 0000000..d00c770 --- /dev/null +++ b/lib/view/components/search/search_result_body.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:unofficial_jisho_api/api.dart'; + +import 'search_results_body/search_card.dart'; + +class SearchResultsBody extends StatelessWidget { + final List results; + + const SearchResultsBody({ + required this.results, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListView( + children: results.map((result) => SearchResultCard(result: result)).toList(), + ); + } +} diff --git a/lib/view/components/search/search_result_page/parts/common_badge.dart b/lib/view/components/search/search_result_page/parts/common_badge.dart deleted file mode 100644 index 8bb6acb..0000000 --- a/lib/view/components/search/search_result_page/parts/common_badge.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/badge.dart'; - -class CommonBadge extends StatelessWidget { - final bool isCommon; - - const CommonBadge(this.isCommon); - - @override - Widget build(BuildContext context) { - return Badge( - Text( - "C", - style: TextStyle(color: this.isCommon ? Colors.white : Colors.transparent) - ), - this.isCommon ? Colors.green : Colors.transparent - ); - } -} \ No newline at end of file diff --git a/lib/view/components/search/search_result_page/parts/jlpt_badge.dart b/lib/view/components/search/search_result_page/parts/jlpt_badge.dart deleted file mode 100644 index d0566d6..0000000 --- a/lib/view/components/search/search_result_page/parts/jlpt_badge.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/badge.dart'; - -class JLPTBadge extends StatelessWidget { - final String jlptLevel; - - const JLPTBadge(this.jlptLevel); - - String _extractJlptLevel(String jlptRaw) { - return jlptRaw.isNotEmpty ? jlptRaw.substring(5).toUpperCase() : ''; - } - - @override - Widget build(BuildContext context) { - return Badge( - Text( - _extractJlptLevel(this.jlptLevel), - style: TextStyle( - color: Colors.white - ), - ), - this.jlptLevel.isNotEmpty ? Colors.blue : Colors.transparent - ); - } -} \ No newline at end of file diff --git a/lib/view/components/search/search_result_page/parts/senses.dart b/lib/view/components/search/search_result_page/parts/senses.dart deleted file mode 100644 index 510054d..0000000 --- a/lib/view/components/search/search_result_page/parts/senses.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:unofficial_jisho_api/parser.dart'; - -class Senses extends StatelessWidget { - final List senses; - - const Senses(this.senses); - - @override - Widget build(BuildContext context) { - final List senseWidgets = - senses.asMap().entries.map((e) => _Sense(e.key, e.value)).toList(); - - return Container( - child: Column( - children: senseWidgets, - )); - } -} - -class _Sense extends StatelessWidget { - final int index; - final JishoWordSense sense; - - const _Sense(this.index, this.sense); - - @override - Widget build(BuildContext context) { - return Container( - child: Column( - children: [ - Row( - children: [ - Text( - (index + 1).toString() + '. ', - style: TextStyle(color: Colors.grey), - ), - Text( - sense.partsOfSpeech.join(', '), - style: TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.left, - ), - ], - ), - Container( - child: Row( - children:[ - Column( - children: - sense.englishDefinitions.map((def) => Text(def)).toList(), - crossAxisAlignment: CrossAxisAlignment.start, - ), - ] - ), - padding: EdgeInsets.symmetric(horizontal: 20), - margin: EdgeInsets.fromLTRB(0, 5, 0, 15), - ), - ], - ), - ); - } -} diff --git a/lib/view/components/search/search_result_page/parts/wanikani_badge.dart b/lib/view/components/search/search_result_page/parts/wanikani_badge.dart deleted file mode 100644 index a24f9f8..0000000 --- a/lib/view/components/search/search_result_page/parts/wanikani_badge.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/badge.dart'; - -class WKBadge extends StatelessWidget { - final String wkLevel; - - const WKBadge(this.wkLevel); - - String _extractWkLevel(String wkRaw) { - // return jlptRaw.isNotEmpty ? jlptRaw.substring(5).toUpperCase() : ''; - return wkRaw.isNotEmpty ? 'W' + wkRaw.substring(8) : ''; - } - - @override - Widget build(BuildContext context) { - return Badge( - Text( - _extractWkLevel(this.wkLevel), - style: TextStyle( - color: Colors.white, - ), - ), - this.wkLevel.isNotEmpty ? Colors.red : Colors.transparent - ); - } -} \ No newline at end of file diff --git a/lib/view/components/search/search_result_page/parts/badge.dart b/lib/view/components/search/search_results_body/parts/badge.dart similarity index 59% rename from lib/view/components/search/search_result_page/parts/badge.dart rename to lib/view/components/search/search_results_body/parts/badge.dart index 3ddf3aa..457fc01 100644 --- a/lib/view/components/search/search_result_page/parts/badge.dart +++ b/lib/view/components/search/search_results_body/parts/badge.dart @@ -1,27 +1,27 @@ import 'package:flutter/material.dart'; class Badge extends StatelessWidget { - final Widget child; + final Widget? child; final Color color; - const Badge(this.child, this.color); + const Badge({this.child, required this.color, Key? key,}) : super(key: key); + @override Widget build(BuildContext context) { return Container( - child: FittedBox( - child: Center( - child: this.child - ), - ), - padding: EdgeInsets.all(5), + padding: const EdgeInsets.all(5), width: 30, height: 30, - margin: EdgeInsets.symmetric(horizontal: 2), + margin: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( shape: BoxShape.circle, - color: color + color: color, + ), + child: FittedBox( + child: Center( + child: child, + ), ), ); } - -} \ No newline at end of file +} diff --git a/lib/view/components/search/search_results_body/parts/common_badge.dart b/lib/view/components/search/search_results_body/parts/common_badge.dart new file mode 100644 index 0000000..bdcaad2 --- /dev/null +++ b/lib/view/components/search/search_results_body/parts/common_badge.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import './badge.dart'; + +class CommonBadge extends StatelessWidget { + final bool isCommon; + + const CommonBadge({ + required this.isCommon, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Badge( + color: isCommon ? Colors.green : Colors.transparent, + child: Text( + 'C', + style: TextStyle( + color: isCommon ? Colors.white : Colors.transparent, + ), + ), + ); + } +} diff --git a/lib/view/components/search/search_result_page/parts/header.dart b/lib/view/components/search/search_results_body/parts/header.dart similarity index 53% rename from lib/view/components/search/search_result_page/parts/header.dart rename to lib/view/components/search/search_results_body/parts/header.dart index 677ae37..46eaba4 100644 --- a/lib/view/components/search/search_result_page/parts/header.dart +++ b/lib/view/components/search/search_results_body/parts/header.dart @@ -3,21 +3,24 @@ import 'package:unofficial_jisho_api/api.dart'; class JapaneseHeader extends StatelessWidget { final JishoJapaneseWord word; - - const JapaneseHeader(this.word); + + const JapaneseHeader({ + required this.word, + Key? key, + }) : super(key: key); + + bool get hasFurigana => word.word != null && word.reading != null; @override Widget build(BuildContext context) { - final hasFurigana = (word.word != null && word.reading != null); - return Container( alignment: Alignment.centerLeft, - padding: EdgeInsets.only(left: 10.0), + padding: const EdgeInsets.only(left: 10.0), child: Column( children: [ // TODO: take a look at this logic - (hasFurigana) ? Text(word.reading!) : Text(''), - (hasFurigana) ? Text(word.word!) : Text(word.reading ?? word.word!), + hasFurigana ? Text(word.reading!) : const Text(''), + hasFurigana ? Text(word.word!) : Text(word.reading ?? word.word!), ], ), ); diff --git a/lib/view/components/search/search_results_body/parts/jlpt_badge.dart b/lib/view/components/search/search_results_body/parts/jlpt_badge.dart new file mode 100644 index 0000000..77e97c5 --- /dev/null +++ b/lib/view/components/search/search_results_body/parts/jlpt_badge.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import './badge.dart'; + +class JLPTBadge extends StatelessWidget { + final String jlptLevel; + + const JLPTBadge({ + required this.jlptLevel, + Key? key, + }) : super(key: key); + + String get formattedJlptLevel => + jlptLevel.isNotEmpty ? jlptLevel.substring(5).toUpperCase() : ''; + + @override + Widget build(BuildContext context) { + return Badge( + color: jlptLevel.isNotEmpty ? Colors.blue : Colors.transparent, + child: Text( + formattedJlptLevel, + style: const TextStyle(color: Colors.white), + ), + ); + } +} diff --git a/lib/view/components/search/search_result_page/parts/other_forms.dart b/lib/view/components/search/search_results_body/parts/other_forms.dart similarity index 54% rename from lib/view/components/search/search_result_page/parts/other_forms.dart rename to lib/view/components/search/search_results_body/parts/other_forms.dart index 8ea8dc1..d690f8c 100644 --- a/lib/view/components/search/search_result_page/parts/other_forms.dart +++ b/lib/view/components/search/search_results_body/parts/other_forms.dart @@ -1,28 +1,27 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; import 'package:unofficial_jisho_api/api.dart'; -class OtherForms extends StatelessWidget { - final List otherForms; +import '../../../../../bloc/theme/theme_bloc.dart'; - const OtherForms(this.otherForms); +class OtherForms extends StatelessWidget { + final List forms; + + const OtherForms({required this.forms, Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Container( - child: Column( - children: this.otherForms.isNotEmpty - ? [ - Text( - 'Other Forms', - style: TextStyle(fontWeight: FontWeight.bold), - ), - Row( - children: otherForms.map((form) => _KanaBox(form)).toList(), - ), - ] - : [], - ), + return Column( + children: forms.isNotEmpty + ? [ + const Text( + 'Other Forms', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Row( + children: forms.map((form) => _KanaBox(form)).toList(), + ), + ] + : [], ); } } @@ -32,28 +31,19 @@ class _KanaBox extends StatelessWidget { const _KanaBox(this.word); + bool get hasFurigana => word.word != null; + @override Widget build(BuildContext context) { - final hasFurigana = (word.word != null); final _menuColors = BlocProvider.of(context).state.theme.menuGreyLight; return Container( - child: DefaultTextStyle.merge( - child: Column( - children: [ - // TODO: take a look at this logic - (hasFurigana) ? Text(word.reading ?? '') : Text(''), - (hasFurigana) ? Text(word.word!) : Text(word.reading ?? ''), - ], - ), - style: TextStyle(color: _menuColors.foreground), - ), - margin: EdgeInsets.symmetric( + margin: const EdgeInsets.symmetric( horizontal: 5.0, vertical: 5.0, ), - padding: EdgeInsets.all(5.0), + padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: _menuColors.background, boxShadow: [ @@ -61,10 +51,20 @@ class _KanaBox extends StatelessWidget { color: Colors.grey.withOpacity(0.5), spreadRadius: 1, blurRadius: 0.5, - offset: Offset(1, 1), + offset: const Offset(1, 1), ), ], ), + child: DefaultTextStyle.merge( + child: Column( + children: [ + // TODO: take a look at this logic + hasFurigana ? Text(word.reading ?? '') : const Text(''), + hasFurigana ? Text(word.word!) : Text(word.reading ?? ''), + ], + ), + style: TextStyle(color: _menuColors.foreground), + ), ); } } diff --git a/lib/view/components/search/search_results_body/parts/senses.dart b/lib/view/components/search/search_results_body/parts/senses.dart new file mode 100644 index 0000000..1bd0ccc --- /dev/null +++ b/lib/view/components/search/search_results_body/parts/senses.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:unofficial_jisho_api/parser.dart'; + +class Senses extends StatelessWidget { + final List senses; + + const Senses({ + required this.senses, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final List senseWidgets = + senses.asMap().entries.map((e) => _Sense(e.key, e.value)).toList(); + + return Column( + children: senseWidgets, + ); + } +} + +class _Sense extends StatelessWidget { + final int index; + final JishoWordSense sense; + + const _Sense(this.index, this.sense); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Text( + '${index + 1}. ', + style: const TextStyle(color: Colors.grey), + ), + Text( + sense.partsOfSpeech.join(', '), + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.left, + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + margin: const EdgeInsets.fromLTRB(0, 5, 0, 15), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + sense.englishDefinitions.map((def) => Text(def)).toList(), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/view/components/search/search_results_body/parts/wanikani_badge.dart b/lib/view/components/search/search_results_body/parts/wanikani_badge.dart new file mode 100644 index 0000000..96df9c8 --- /dev/null +++ b/lib/view/components/search/search_results_body/parts/wanikani_badge.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import './badge.dart'; + +class WKBadge extends StatelessWidget { + final String level; + + const WKBadge({ + required this.level, + Key? key, + }) : super(key: key); + + String _extractWkLevel(String wkRaw) { + return wkRaw.isNotEmpty ? 'W${wkRaw.substring(8)}' : ''; + } + + @override + Widget build(BuildContext context) { + return Badge( + color: level.isNotEmpty ? Colors.red : Colors.transparent, + child: Text( + _extractWkLevel(level), + style: const TextStyle( + color: Colors.white, + ), + ), + ); + } +} diff --git a/lib/view/screens/kanji/search/grade_list.dart b/lib/view/components/search/search_results_body/parts/wikipedia_attribute.dart similarity index 100% rename from lib/view/screens/kanji/search/grade_list.dart rename to lib/view/components/search/search_results_body/parts/wikipedia_attribute.dart diff --git a/lib/view/components/search/search_result_page/search_card.dart b/lib/view/components/search/search_results_body/search_card.dart similarity index 56% rename from lib/view/components/search/search_result_page/search_card.dart rename to lib/view/components/search/search_results_body/search_card.dart index 4b045dd..d4c8129 100644 --- a/lib/view/components/search/search_result_page/search_card.dart +++ b/lib/view/components/search/search_results_body/search_card.dart @@ -1,23 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/common_badge.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/jlpt_badge.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/parts/wanikani_badge.dart'; - import 'package:unofficial_jisho_api/api.dart'; +import './parts/common_badge.dart'; import './parts/header.dart'; -import './parts/senses.dart'; +import './parts/jlpt_badge.dart'; import './parts/other_forms.dart'; +import './parts/senses.dart'; +import './parts/wanikani_badge.dart'; class SearchResultCard extends StatelessWidget { final JishoResult result; late final JishoJapaneseWord mainWord; late final List otherForms; - SearchResultCard(this.result) { - this.mainWord = result.japanese[0]; - this.otherForms = result.japanese.sublist(1); - } + SearchResultCard({ + required this.result, + Key? key, + }) : mainWord = result.japanese[0], + otherForms = result.japanese.sublist(1), + super(key: key); @override Widget build(BuildContext context) { @@ -25,34 +26,40 @@ class SearchResultCard extends StatelessWidget { return ExpansionTile( collapsedBackgroundColor: backgroundColor, backgroundColor: backgroundColor, - title: - IntrinsicWidth( + title: IntrinsicWidth( child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - JapaneseHeader(mainWord), + JapaneseHeader(word: mainWord), Row( children: [ - WKBadge(result.tags.firstWhere((tag) => tag.contains("wanikani"), orElse: () => '')), - JLPTBadge(result.jlpt.isNotEmpty ? result.jlpt[0] : ''), - CommonBadge(result.isCommon ?? false) + WKBadge( + level: result.tags.firstWhere( + (tag) => tag.contains('wanikani'), + orElse: () => '', + ), + ), + JLPTBadge( + jlptLevel: result.jlpt.isNotEmpty ? result.jlpt[0] : '', + ), + CommonBadge(isCommon: result.isCommon ?? false) ], ) ], - mainAxisAlignment: MainAxisAlignment.spaceBetween, ), ), children: [ Container( + padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( children: [ - Senses(result.senses), - OtherForms(otherForms), + Senses(senses: result.senses), + OtherForms(forms: otherForms), // Text(result.toJson().toString()), // Text(result.attribution.toJson().toString()), // Text(result.japanese.map((e) => e.toJson().toString()).toList().toString()), ], ), - padding: EdgeInsets.symmetric(horizontal: 30), ) ], ); diff --git a/lib/view/screens/search/details.dart b/lib/view/components/search/word_details_body.dart similarity index 100% rename from lib/view/screens/search/details.dart rename to lib/view/components/search/word_details_body.dart diff --git a/lib/view/home.dart b/lib/view/home.dart new file mode 100644 index 0000000..90e42f5 --- /dev/null +++ b/lib/view/home.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:mdi/mdi.dart'; + +import '../bloc/theme/theme_bloc.dart'; +import 'screens/history.dart'; +import 'screens/search/kanji_view.dart'; +import 'screens/search/search_view.dart'; +import 'screens/settings.dart'; + +class Home extends StatefulWidget { + const Home({Key? key}) : super(key: key); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + int pageNum = 0; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, themeState) { + return Scaffold( + appBar: AppBar( + title: pages[pageNum].titleBar, + centerTitle: true, + backgroundColor: AppTheme.jishoGreen.background, + foregroundColor: AppTheme.jishoGreen.foreground, + ), + body: Stack( + children: [ + Positioned( + right: 30, + left: 100, + bottom: 30, + child: Image.asset( + 'assets/images/denshi_jisho_background_overlay.png', + ), + ), + pages[pageNum].content, + ], + ), + bottomNavigationBar: BottomNavigationBar( + fixedColor: AppTheme.jishoGreen.background, + currentIndex: pageNum, + onTap: (index) => setState(() { + pageNum = index; + }), + items: pages.map((p) => p.item).toList(), + showSelectedLabels: false, + showUnselectedLabels: false, + unselectedItemColor: themeState.theme.menuGreyDark.background, + ), + ); + }, + ); + } + + List<_Page> get pages => [ + const _Page( + content: SearchView(), + titleBar: Text('Search'), + item: BottomNavigationBarItem( + label: 'Search', + icon: Icon(Icons.search), + ), + ), + const _Page( + content: KanjiView(), + titleBar: Text('Kanji'), + item: BottomNavigationBarItem( + label: 'Kanji', + icon: Icon(Mdi.ideogramCjk, size: 30), + ), + ), + const _Page( + content: HistoryView(), + titleBar: Text('History'), + item: BottomNavigationBarItem( + label: 'History', + icon: Icon(Icons.history), + ), + ), + _Page( + content: Container(), + titleBar: const Text('Saved'), + item: const BottomNavigationBarItem( + label: 'Saved', + icon: Icon(Icons.bookmark), + ), + ), + const _Page( + content: SettingsView(), + titleBar: Text('Settings'), + item: BottomNavigationBarItem( + label: 'Settings', + icon: Icon(Icons.settings), + ), + ), + ]; +} + +class _Page { + final Widget content; + final Widget titleBar; + final BottomNavigationBarItem item; + + const _Page({ + required this.content, + required this.titleBar, + required this.item, + }); +} diff --git a/lib/view/screens/history.dart b/lib/view/screens/history.dart index 0a45b19..f413512 100644 --- a/lib/view/screens/history.dart +++ b/lib/view/screens/history.dart @@ -1,80 +1,113 @@ import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/database/database_bloc.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/phrase_search_item.dart'; -import 'package:jisho_study_tool/view/components/history/date_divider.dart'; +import 'package:get_it/get_it.dart'; +import 'package:jisho_study_tool/view/components/common/loading.dart'; +import 'package:sembast/sembast.dart'; -import 'package:jisho_study_tool/objectbox.g.dart'; -import 'package:jisho_study_tool/view/components/opaque_box.dart'; +import '../../models/history/search.dart'; +import '../components/history/date_divider.dart'; +import '../components/history/kanji_search_item.dart'; +import '../components/history/phrase_search_item.dart'; +import '../components/opaque_box.dart'; class HistoryView extends StatelessWidget { + const HistoryView({Key? key}) : super(key: key); + + Database get _db => GetIt.instance.get(); + + Stream> get searchStream => Search.store + .query( + finder: Finder( + sortOrders: [SortOrder('timestamp', false)], + ), + ) + .onSnapshots(_db) + .map((snapshot) { + return snapshot + .map( + (snap) => (snap.value != null) + ? Search.fromJson(snap.value! as Map) + : null, + ) + .where((s) => s != null) + .map((s) => s!) + .toList(); + }); + @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is DatabaseDisconnected) - throw DatabaseNotConnectedException(); + return StreamBuilder>( + stream: searchStream, + builder: (context, snapshot) { + if (!snapshot.hasData) return const LoadingScreen(); - 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 OpaqueBox( - child: 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); + final List data = snapshot.data!; + if (data.isEmpty) + return const Center( + child: Text('The history is empty.\nTry searching for something!'), + ); - Search search = snapshot.data[index]; - DateTime searchDate = roundToDay(search.timestamp); - - bool newDate = true; - - EdgeInsets? margin; - if (index != 0) { - Search prevSearch = snapshot.data[index - 1]; - - DateTime prevSearchDate = roundToDay(prevSearch.timestamp); - newDate = prevSearchDate != searchDate; - margin = EdgeInsets.only(bottom: 10); - } - - if (newDate) { - if (searchDate == roundToDay(DateTime.now())) - return DateDivider(text: "Today", margin: margin); - else if (searchDate == - roundToDay( - DateTime.now().subtract(const Duration(days: 1)))) - return DateDivider(text: "Yesterday", margin: margin); - return DateDivider(date: searchDate, margin: margin); - } - - return Divider(); - }, - ), - ); - }, + return OpaqueBox( + child: ListView.separated( + itemCount: data.length + 1, + itemBuilder: historyEntryWithData(data), + separatorBuilder: historyEntrySeparatorWithData(data), + ), ); }, ); } + + Widget Function(BuildContext, int) historyEntryWithData(List data) => + (context, index) { + if (index == 0) return Container(); + + final Search search = data[index - 1]; + + return (search.isKanji) + ? KanjiSearchItem( + result: search.kanjiQuery!, + timestamp: search.timestamp, + ) + : PhraseSearchItem( + search: search.wordQuery!, + timestamp: search.timestamp, + ); + }; + + DateTime roundToDay(DateTime date) => + DateTime(date.year, date.month, date.day); + + bool dateChangedFromLastSearch(Search prevSearch, DateTime searchDate) { + final DateTime prevSearchDate = roundToDay(prevSearch.timestamp); + return prevSearchDate != searchDate; + } + + DateTime get today => roundToDay(DateTime.now()); + DateTime get yesterday => + roundToDay(DateTime.now().subtract(const Duration(days: 1))); + + Widget Function(BuildContext, int) historyEntrySeparatorWithData( + List data, + ) => + (context, index) { + final Search search = data[index]; + final DateTime searchDate = roundToDay(search.timestamp); + + EdgeInsets? margin; + if (index != 0) { + margin = const EdgeInsets.only(bottom: 10); + } + + if (index == 0 || + dateChangedFromLastSearch(data[index - 1], searchDate)) { + if (searchDate == today) + return DateDivider(text: 'Today', margin: margin); + else if (searchDate == yesterday) + return DateDivider(text: 'Yesterday', margin: margin); + else + return DateDivider(date: searchDate, margin: margin); + } + + return const Divider(); + }; } diff --git a/lib/view/screens/kanji/result.dart b/lib/view/screens/kanji/result.dart deleted file mode 100644 index 3e3a754..0000000 --- a/lib/view/screens/kanji/result.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:unofficial_jisho_api/api.dart' as jisho; - -import 'package:jisho_study_tool/view/components/kanji/result/examples.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/grade.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/header.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/jlpt_level.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/radical.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/rank.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/stroke_order_gif.dart'; -import 'package:jisho_study_tool/view/components/kanji/result/yomi_chips.dart'; - -class KanjiResultCard extends StatelessWidget { - late final String query; - late final jisho.KanjiResultData resultData; - - KanjiResultCard({required jisho.KanjiResult result}) { - - query = result.query; - - // TODO: Handle this kind of exception before widget is initialized - if (result.data == null) - throw Exception(); - - resultData = result.data!; - } - - @override - Widget build(BuildContext context) { - return ListView( - children: [ - Container( - margin: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - flex: 1, - fit: FlexFit.tight, - child: Center(child: SizedBox()), - ), - Flexible( - flex: 1, - fit: FlexFit.tight, - child: Center(child: Header(query)), - ), - Flexible( - flex: 1, - fit: FlexFit.tight, - child: Center( - child: (resultData.radical != null) ? Radical(resultData.radical!) : SizedBox(), - ), - ), - ], - ), - ), - YomiChips(resultData.meaning.split(', '), YomiType.meaning), - resultData.onyomi.length != 0 ? YomiChips(resultData.onyomi, YomiType.onyomi) : SizedBox.shrink(), - resultData.kunyomi.length != 0 ? YomiChips(resultData.kunyomi, YomiType.kunyomi) : SizedBox.shrink(), - IntrinsicHeight( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - StrokeOrderGif(resultData.strokeOrderGifUri), - Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text("JLPT: ", style: TextStyle(fontSize: 20.0)), - JlptLevel(resultData.jlptLevel ?? "⨉"), - ], - ), - Row( - children: [ - Text("Grade: ", style: TextStyle(fontSize: 20.0)), - Grade(resultData.taughtIn ?? "⨉"), - ], - ), - Row( - children: [ - Text("Rank: ", style: TextStyle(fontSize: 20.0)), - Rank(resultData.newspaperFrequencyRank ?? -1), - ], - ), - ], - ), - ), - ], - ), - ), - Examples(resultData.onyomiExamples, resultData.kunyomiExamples), - ], - ); - } -} \ No newline at end of file diff --git a/lib/view/screens/kanji/search.dart b/lib/view/screens/kanji/search.dart deleted file mode 100644 index 0c3d803..0000000 --- a/lib/view/screens/kanji/search.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart'; -import 'package:animated_size_and_fade/animated_size_and_fade.dart'; - -import 'package:jisho_study_tool/view/components/kanji/kanji_grid.dart'; -import 'package:jisho_study_tool/view/components/kanji/kanji_search_bar.dart'; -import 'package:jisho_study_tool/view/components/kanji/kanji_search_options_bar.dart'; - -class SearchScreen extends StatefulWidget { - SearchScreen({Key? key}) : super(key: key); - - @override - _SearchScreenState createState() => _SearchScreenState(); -} - -class _SearchScreenState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _controller; - late final Animation _searchbarMovementAnimation; - - @override - void initState() { - super.initState(); - - _controller = AnimationController( - vsync: this, - duration: Duration(milliseconds: 200), - ); - - _searchbarMovementAnimation = AlignmentTween( - begin: Alignment.center, - end: Alignment.topCenter, - ).animate(CurvedAnimation( - parent: _controller, - curve: Curves.easeInOut, - )); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: Container( - decoration: BoxDecoration(), - alignment: Alignment.center, - padding: EdgeInsets.symmetric(horizontal: 20), - child: AnimatedBuilder( - animation: _searchbarMovementAnimation, - builder: (BuildContext context, _) { - return Container( - alignment: _searchbarMovementAnimation.value, - padding: EdgeInsets.symmetric(vertical: 10.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Focus( - onFocusChange: (hasFocus) { - if (hasFocus) - _controller.forward(); - else - _controller.reverse(); - }, - child: KanjiSearchBar(), - ), - BlocBuilder( - builder: (context, state) { - return AnimatedSizeAndFade( - vsync: this, - child: _controller.value == 1 - ? KanjiGrid((state is KanjiSearchKeyboard) - ? state.kanjiSuggestions - : []) - // ? Container() - : KanjiSearchOptionsBar(), - fadeDuration: const Duration(milliseconds: 200), - sizeDuration: const Duration(milliseconds: 300), - ); - }, - ) - ], - ), - ); - }, - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/view/screens/kanji/search/grid.dart b/lib/view/screens/kanji/search/grid.dart deleted file mode 100644 index 5aaca73..0000000 --- a/lib/view/screens/kanji/search/grid.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/view/components/kanji/kanji_grid.dart'; -import 'package:jisho_study_tool/view/components/kanji/kanji_search_bar.dart'; - -class SearchGrid extends StatelessWidget { - final List suggestions; - const SearchGrid(this.suggestions); - - @override - Widget build(BuildContext context) { - return - Column( - children: [ - SizedBox(height: 10), - KanjiSearchBar(), - SizedBox(height: 10), - Expanded( - child: KanjiGrid(suggestions) - ) - ], - ); - } -} \ No newline at end of file diff --git a/lib/view/screens/kanji/search/radical_list.dart b/lib/view/screens/kanji/search/radical_list.dart deleted file mode 100644 index 8d93387..0000000 --- a/lib/view/screens/kanji/search/radical_list.dart +++ /dev/null @@ -1 +0,0 @@ -const List> radicals = [[]]; \ No newline at end of file diff --git a/lib/view/screens/kanji/view.dart b/lib/view/screens/kanji/view.dart deleted file mode 100644 index 4dfd38b..0000000 --- a/lib/view/screens/kanji/view.dart +++ /dev/null @@ -1,71 +0,0 @@ - -import 'package:flutter/material.dart'; - -import 'package:jisho_study_tool/bloc/kanji/kanji_bloc.dart'; -import 'package:jisho_study_tool/view/screens/loading.dart'; - -import 'search.dart'; -import 'result.dart'; - -class KanjiView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state is KanjiSearch && state.type == KanjiSearchType.Initial) { - FocusScope.of(context).unfocus(); - } else if (state is KanjiSearchLoading) { - FocusScope.of(context).unfocus(); - } - }, - child: BlocBuilder( - builder: (context, state) { - if (state is KanjiSearch) { - if (state.type == KanjiSearchType.Initial) return SearchScreen(); - else if (state is KanjiSearchKeyboard) return SearchScreen(); - } - else if (state is KanjiSearchLoading) return LoadingScreen(); - else if (state is KanjiSearchFinished) - return WillPopScope( - child: KanjiResultCard(result: state.kanji), - onWillPop: () async { - BlocProvider.of(context) - .add(ReturnToInitialState()); - return false; - }); - throw 'No such event found'; - }, - ), - ); - } -} - -class KanjiViewBar extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Container( - child: Row( - children: [ - IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () => - BlocProvider.of(context).add(ReturnToInitialState()), - ), - // Expanded( - // child: Container( - // child: KanjiSearchBar(), - // ), - // ), - // IconButton( - // icon: Icon(Icons.star_border), - // onPressed: null, - // ), - // IconButton( - // icon: Icon(Icons.add), - // onPressed: null, - // ), - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/view/screens/search/kanji_result_page.dart b/lib/view/screens/search/kanji_result_page.dart new file mode 100644 index 0000000..07a6f2b --- /dev/null +++ b/lib/view/screens/search/kanji_result_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:sembast/sembast.dart'; + +import '../../../models/history/kanji_query.dart'; +import '../../../models/history/search.dart'; +import '../../../services/jisho_api/kanji_search.dart'; +import '../../components/common/loading.dart'; +import '../../components/kanji/kanji_result_body.dart'; + +class KanjiResultPage extends StatelessWidget { + final String kanjiSearchTerm; + bool addedToDatabase = false; + + KanjiResultPage({required this.kanjiSearchTerm, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: FutureBuilder( + future: fetchKanji(kanjiSearchTerm), + builder: (context, snapshot) { + if (!snapshot.hasData) return const LoadingScreen(); + if (snapshot.hasError) return ErrorWidget(snapshot.error!); + + if (!addedToDatabase) { + Search.store.add( + GetIt.instance.get(), + Search.fromKanjiQuery( + timestamp: DateTime.now(), + kanjiQuery: KanjiQuery(kanji: kanjiSearchTerm), + ).toJson(), + ); + addedToDatabase = true; + } + + return KanjiResultBody(result: snapshot.data!); + }, + ), + ); + } +} diff --git a/lib/view/screens/search/kanji_view.dart b/lib/view/screens/search/kanji_view.dart new file mode 100644 index 0000000..77f8042 --- /dev/null +++ b/lib/view/screens/search/kanji_view.dart @@ -0,0 +1,13 @@ + +import 'package:flutter/material.dart'; + +import '../../components/kanji/kanji_search_body.dart'; + +class KanjiView extends StatelessWidget { + const KanjiView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const KanjiSearchBody(); + } +} diff --git a/lib/view/screens/search/results.dart b/lib/view/screens/search/results.dart deleted file mode 100644 index 8f42aff..0000000 --- a/lib/view/screens/search/results.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/search/search_bloc.dart'; -import 'package:jisho_study_tool/view/components/search/search_result_page/search_card.dart'; -import 'package:unofficial_jisho_api/api.dart'; - -class SearchResults extends StatelessWidget { - final List results; - - const SearchResults({ - required this.results, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return WillPopScope( - child: ListView( - children: results.map((result) => SearchResultCard(result)).toList(), - ), - onWillPop: () async { - BlocProvider.of(context).add(ReturnToInitialState()); - return false; - }, - ); - } -} diff --git a/lib/view/screens/search/search_mechanisms/drawing.dart b/lib/view/screens/search/search_mechanisms/drawing.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/view/screens/search/search_mechanisms/grade_list.dart b/lib/view/screens/search/search_mechanisms/grade_list.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/view/screens/search/search_mechanisms/grid.dart b/lib/view/screens/search/search_mechanisms/grid.dart new file mode 100644 index 0000000..742e0a0 --- /dev/null +++ b/lib/view/screens/search/search_mechanisms/grid.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../../../components/kanji/kanji_search_body/kanji_grid.dart'; +import '../../../components/kanji/kanji_search_body/kanji_search_bar.dart'; + +class SearchGrid extends StatelessWidget { + final List suggestions; + + const SearchGrid({ + required this.suggestions, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const SizedBox(height: 10), + const KanjiSearchBar(), + const SizedBox(height: 10), + Expanded(child: KanjiGrid(suggestions: suggestions)) + ], + ); + } +} diff --git a/lib/view/screens/search/search_mechanisms/radical_list.dart b/lib/view/screens/search/search_mechanisms/radical_list.dart new file mode 100644 index 0000000..a6ed8a2 --- /dev/null +++ b/lib/view/screens/search/search_mechanisms/radical_list.dart @@ -0,0 +1 @@ +const List> radicals = [[]]; diff --git a/lib/view/screens/search/search_results_page.dart b/lib/view/screens/search/search_results_page.dart new file mode 100644 index 0000000..96f779a --- /dev/null +++ b/lib/view/screens/search/search_results_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:sembast/sembast.dart'; + +import '../../../models/history/search.dart'; +import '../../../models/history/word_query.dart'; +import '../../../services/jisho_api/jisho_search.dart'; +import '../../components/common/loading.dart'; +import '../../components/search/search_result_body.dart'; + +class SearchResultsPage extends StatelessWidget { + final String searchTerm; + final Future results; + bool addedToDatabase = false; + + SearchResultsPage({required this.searchTerm, Key? key}) + : results = fetchJishoResults(searchTerm), + super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: FutureBuilder( + future: results, + builder: (context, snapshot) { + if (!snapshot.hasData) return const LoadingScreen(); + if (snapshot.hasError || snapshot.data!.data == null) + return ErrorWidget(snapshot.error!); + + if (!addedToDatabase) { + Search.store.add( + GetIt.instance.get(), + Search.fromWordQuery( + timestamp: DateTime.now(), + wordQuery: WordQuery(query: searchTerm), + ).toJson(), + ); + addedToDatabase = true; + } + + return SearchResultsBody( + results: snapshot.data!.data!, + ); + }, + ), + ); + } +} diff --git a/lib/view/screens/search/search_view.dart b/lib/view/screens/search/search_view.dart new file mode 100644 index 0000000..d68be17 --- /dev/null +++ b/lib/view/screens/search/search_view.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import '../../components/search/search_bar.dart'; + +class SearchView extends StatelessWidget { + const SearchView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + SearchBar(), + ], + ); + } +} diff --git a/lib/view/screens/search/view.dart b/lib/view/screens/search/view.dart deleted file mode 100644 index 651a7c5..0000000 --- a/lib/view/screens/search/view.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/search/search_bloc.dart'; -import 'package:jisho_study_tool/view/components/search/search_bar.dart'; -import 'package:jisho_study_tool/view/screens/loading.dart'; -import 'package:jisho_study_tool/view/screens/search/results.dart'; - -class SearchView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - if (state is SearchInitial) - return _InitialView(); - else if (state is SearchLoading) - return LoadingScreen(); - else if (state is SearchFinished) { - return SearchResults(results: state.results); - } - throw 'No such event found'; - }, - ); - } -} - -class _InitialView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - SearchBar(), - ]); - } -} diff --git a/lib/view/screens/settings.dart b/lib/view/screens/settings.dart index 40d4709..4723513 100644 --- a/lib/view/screens/settings.dart +++ b/lib/view/screens/settings.dart @@ -1,22 +1,23 @@ import 'package:confirm_dialog/confirm_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:jisho_study_tool/bloc/database/database_bloc.dart'; -import 'package:jisho_study_tool/bloc/theme/theme_bloc.dart'; -import 'package:jisho_study_tool/models/history/search.dart'; -import 'package:jisho_study_tool/models/themes/theme.dart'; -import 'package:jisho_study_tool/objectbox.g.dart'; +import 'package:get_it/get_it.dart'; +import 'package:sembast/sembast.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../bloc/theme/theme_bloc.dart'; +import '../../models/history/search.dart'; +import '../../models/themes/theme.dart'; + class SettingsView extends StatefulWidget { - SettingsView({Key? key}) : super(key: key); + const SettingsView({Key? key}) : super(key: key); @override _SettingsViewState createState() => _SettingsViewState(); } class _SettingsViewState extends State { - late final SharedPreferences prefs; + final SharedPreferences prefs = GetIt.instance.get(); bool darkThemeEnabled = false; bool autoThemeEnabled = false; @@ -24,41 +25,41 @@ class _SettingsViewState extends State { @override void initState() { super.initState(); - - SharedPreferences.getInstance().then((prefs) { - this.prefs = prefs; - _getPrefs(); - }); - } - - /// Get stored preferences and set setting page state accordingly - void _getPrefs() { - setState(() { - darkThemeEnabled = prefs.getBool('darkThemeEnabled') ?? darkThemeEnabled; - autoThemeEnabled = prefs.getBool('autoThemeEnabled') ?? autoThemeEnabled; - }); + darkThemeEnabled = prefs.getBool('darkThemeEnabled') ?? darkThemeEnabled; + autoThemeEnabled = prefs.getBool('autoThemeEnabled') ?? autoThemeEnabled; } /// Update stored preferences with values from setting page state - void _updatePrefs() async { - await prefs.setBool('darkThemeEnabled', darkThemeEnabled); - await prefs.setBool('autoThemeEnabled', autoThemeEnabled); + Future _updatePrefs() async { + prefs.setBool('darkThemeEnabled', darkThemeEnabled); + prefs.setBool('autoThemeEnabled', autoThemeEnabled); } + Future clearHistory(context) async { + final bool userIsSure = await confirm(context); + + if (userIsSure) { + final Database db = GetIt.instance.get(); + await Search.store.delete(db); + } + } + + @override Widget build(BuildContext context) { final TextStyle _titleTextStyle = TextStyle( - color: BlocProvider.of(context).state is DarkThemeState - ? AppTheme.jishoGreen.background - : null); + color: BlocProvider.of(context).state is DarkThemeState + ? AppTheme.jishoGreen.background + : null, + ); return SettingsList( backgroundColor: Colors.transparent, - contentPadding: EdgeInsets.symmetric(vertical: 10), - sections: [ + contentPadding: const EdgeInsets.symmetric(vertical: 10), + sections: [ SettingsSection( title: 'Theme', titleTextStyle: _titleTextStyle, - tiles: [ + tiles: [ SettingsTile.switchTile( title: 'Automatically determine theme', onToggle: (b) { @@ -74,7 +75,8 @@ class _SettingsViewState extends State { SettingsTile.switchTile( title: 'Dark Theme', onToggle: (b) { - BlocProvider.of(context).add(SetTheme(themeIsDark: b)); + BlocProvider.of(context) + .add(SetTheme(themeIsDark: b)); setState(() { darkThemeEnabled = b; }); @@ -89,7 +91,7 @@ class _SettingsViewState extends State { SettingsSection( title: 'Cache', titleTextStyle: _titleTextStyle, - tiles: [ + tiles: [ SettingsTile.switchTile( title: 'Cache grade 1-7 kanji', switchValue: false, @@ -123,24 +125,23 @@ class _SettingsViewState extends State { SettingsSection( title: 'Data', titleTextStyle: _titleTextStyle, - tiles: [ - SettingsTile( + tiles: [ + const SettingsTile( leading: Icon(Icons.file_download), title: 'Export Data', enabled: false, ), SettingsTile( - leading: Icon(Icons.delete), + leading: const Icon(Icons.delete), title: 'Clear History', - onPressed: _clearHistory, - titleTextStyle: TextStyle(color: Colors.red), - enabled: false, + onPressed: clearHistory, + titleTextStyle: const TextStyle(color: Colors.red), ), SettingsTile( - leading: Icon(Icons.delete), + leading: const Icon(Icons.delete), title: 'Clear Favourites', onPressed: (c) {}, - titleTextStyle: TextStyle(color: Colors.red), + titleTextStyle: const TextStyle(color: Colors.red), enabled: false, ) ], @@ -149,13 +150,3 @@ class _SettingsViewState extends State { ); } } - -void _clearHistory(context) async { - if (await confirm(context)) { - Store db = - (BlocProvider.of(context).state as DatabaseConnected) - .database; - // db.box().query().build().find() - db.box().removeAll(); - } -} diff --git a/pubspec.lock b/pubspec.lock index 62ace01..5161dd6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,35 +7,49 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "22.0.0" + version: "31.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "1.7.2" + version: "2.8.0" + animated_size_and_fade: + dependency: "direct main" + description: + name: animated_size_and_fade + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.6" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.3.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "2.8.1" bloc: dependency: transitive description: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "7.0.0" + version: "8.0.1" boolean_selector: dependency: transitive description: @@ -49,56 +63,56 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.1.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.7" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.0.1" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.12.2" + version: "2.1.5" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.12" + version: "7.2.2" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "5.1.0" + version: "5.1.1" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.1" + version: "8.1.3" characters: dependency: transitive description: @@ -112,7 +126,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -126,7 +140,7 @@ packages: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.5" clock: dependency: transitive description: @@ -140,7 +154,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.1.0" collection: dependency: transitive description: @@ -148,6 +162,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + confirm_dialog: + dependency: "direct main" + description: + name: confirm_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" convert: dependency: transitive description: @@ -168,21 +189,21 @@ packages: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.2" + version: "0.17.1" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.2.0" division: dependency: "direct main" description: name: division url: "https://pub.dartlang.org" source: hosted - version: "0.8.8" + version: "0.9.0" fake_async: dependency: transitive description: @@ -196,14 +217,14 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.2" fixnum: dependency: transitive description: @@ -222,7 +243,28 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "7.0.0" + version: "8.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.2" + flutter_native_splash: + dependency: "direct dev" + description: + name: flutter_native_splash + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -233,41 +275,55 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.0" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.1.0" html: dependency: transitive description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+4" + version: "0.15.0" html_unescape: dependency: transitive description: name: html_unescape url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "2.0.0" http: dependency: transitive description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.4" http_multi_server: dependency: transitive description: @@ -281,7 +337,14 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" io: dependency: transitive description: @@ -302,14 +365,14 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.4.0" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" matcher: dependency: transitive description: @@ -323,21 +386,21 @@ packages: name: mdi url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "5.0.0-nullsafety.0" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.7.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" nested: dependency: transitive description: @@ -345,36 +408,15 @@ 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" + version: "2.0.2" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" @@ -386,56 +428,70 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.7" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.4" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" - pedantic: + version: "2.0.4" + petitparser: dependency: transitive description: - name: pedantic + name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "1.11.0" + version: "4.4.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.2" pool: dependency: transitive description: @@ -449,27 +505,41 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.1" + version: "4.2.4" provider: dependency: transitive description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "6.0.1" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted + version: "1.1.0" + sembast: + dependency: "direct main" + description: + name: sembast + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + settings_ui: + dependency: "direct main" + description: + name: settings_ui + url: "https://pub.dartlang.org" + source: hosted version: "1.0.0" shared_preferences: dependency: "direct main" @@ -477,21 +547,35 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.9" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.3" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_platform_interface: dependency: transitive description: @@ -505,40 +589,33 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.3" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.2.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.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: @@ -574,6 +651,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" term_glyph: dependency: transitive description: @@ -587,7 +671,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.4.2" timing: dependency: transitive description: @@ -602,55 +686,76 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" unofficial_jisho_api: dependency: "direct main" description: name: unofficial_jisho_api url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.0.3" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" + version: "6.0.17" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.13" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.13" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.5" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" vector_math: dependency: transitive description: @@ -664,7 +769,7 @@ packages: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: @@ -678,7 +783,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.3.1" xdg_directories: dependency: transitive description: @@ -686,6 +791,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" yaml: dependency: transitive description: @@ -694,5 +806,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 53933ed..023e5d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,28 +6,27 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - animated_size_and_fade: ^2.0.0 + animated_size_and_fade: ^3.0.0 confirm_dialog: ^1.0.0 division: ^0.9.0 flutter: sdk: flutter - flutter_bloc: ^7.0.1 - flutter_slidable: ^0.6.0 + flutter_bloc: ^8.0.0 + flutter_slidable: ^1.1.0 + get_it: ^7.2.0 mdi: ^5.0.0-nullsafety.0 - objectbox: ^1.1.1 - objectbox_flutter_libs: ^1.1.1 + path: ^1.8.0 path_provider: ^2.0.2 + sembast: ^3.1.1 + settings_ui: ^1.0.0 shared_preferences: ^2.0.6 unofficial_jisho_api: ^2.0.2 url_launcher: ^6.0.9 - settings_ui: ^1.0.0 - dev_dependencies: build_runner: ^2.0.6 flutter_test: sdk: flutter - objectbox_generator: ^1.1.1 flutter_native_splash: ^1.2.0 flutter_launcher_icons: "^0.9.1" @@ -41,6 +40,7 @@ flutter: assets: - assets/images/denshi_jisho_background_overlay.png + - assets/images/logo/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index e8c17e4..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:jisho_study_tool/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}