From 0e3d6b4486ce8f369c158b499a42f8679919e794 Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Fri, 19 Apr 2024 01:24:48 -0400 Subject: [PATCH 1/9] Update dependencies and remove unused code --- coverage/lcov.info | 561 ++++++++++++++++++ lib/common_widgets/app_async_widget.dart | 2 +- lib/common_widgets/app_back_button.dart | 4 +- lib/common_widgets/app_cupertino_button.dart | 2 +- .../app_cupertino_sliver_navigation_bar.dart | 8 +- lib/common_widgets/app_fade_in_image.dart | 6 +- lib/common_widgets/app_web_padding.dart | 4 +- .../app_localization_extension.dart | 2 +- .../extensions/context_snackbar.dart | 0 .../extensions/hardcoded_extension.dart | 0 lib/{ => core}/extensions/intl_extension.dart | 0 .../is_ios_or_macos_platform_extension.dart | 0 .../js_bottom_padding_extension.dart | 0 .../js_bottom_padding_extension_web.dart | 0 .../extensions/router_extension.dart | 0 .../extensions/string_extension.dart | 0 .../theme_of_context_extension.dart | 0 .../network_client_adapter.dart} | 0 .../network_client_adapter_web.dart} | 0 .../client_network_provider.dart | 12 +- .../providers/initialization_provider.dart | 21 + .../providers/initialization_provider.g.dart} | 4 +- .../prefs_provider.dart} | 4 +- .../providers/prefs_provider.g.dart} | 18 +- lib/{ => core}/theme/theme_data.dart | 0 lib/{ => core}/theme/theme_provider.dart | 68 +-- lib/{ => core}/theme/theme_provider.g.dart | 4 +- lib/features/home/home_page.dart | 2 +- lib/features/home/home_page_controller.dart | 2 +- .../home/widgets/adaptive_navigation_bar.dart | 4 +- .../widgets/adaptive_navigation_rail.dart | 2 +- .../adaptive_navigation_rail_footer.dart | 6 +- .../home/widgets/settings_modal_sheet.dart | 4 +- .../home/widgets/settings_theme_section.dart | 6 +- .../details/instrument_details_page.dart | 2 +- .../widgets/instrument_details_learning.dart | 2 +- .../widgets/instrument_header_images.dart | 2 +- .../instruments/instruments_repo.dart | 2 +- .../instruments/instruments_tab_page.dart | 2 +- .../widgets/instrument_list_tile.dart | 2 +- lib/features/parades/parade_extension.dart | 2 +- lib/features/parades/parades_repo.dart | 2 +- lib/features/parades/parades_tab_page.dart | 2 +- .../parades/parades_tab_providers.dart | 2 +- .../parades/parades_tab_providers.g.dart | 2 +- lib/features/parades/widgets/parade_item.dart | 4 +- .../widgets/parade_item_bottom_row.dart | 6 +- .../parades/widgets/parade_item_sidebar.dart | 6 +- .../widgets/parade_item_year_line.dart | 2 +- .../schools/details/school_details_page.dart | 8 +- lib/features/schools/school.dart | 2 +- lib/features/schools/school_color_hook.dart | 2 +- lib/features/schools/school_extensions.dart | 4 +- lib/features/schools/schools_repo.dart | 2 +- .../schools/schools_tab_providers.dart | 8 +- .../schools/schools_tab_providers.g.dart | 6 +- lib/features/schools/widgets/school_card.dart | 8 +- .../schools/widgets/school_filter_chips.dart | 4 +- lib/features/schools/widgets/school_flag.dart | 4 +- .../schools/widgets/schools_empty_list.dart | 4 +- .../schools/widgets/schools_tab_body.dart | 4 +- .../schools/widgets/schools_tab_navbar.dart | 4 +- .../widgets/schools_tab_search_header.dart | 4 +- ...lization_page.dart => initialization.dart} | 56 +- lib/localization/language.dart | 2 +- lib/localization/language_app_provider.dart | 6 +- lib/localization/language_app_provider.g.dart | 2 +- lib/main.dart | 32 +- .../app_router.dart} | 29 +- .../app_router.g.dart} | 21 +- lib/routing/refresh_listenable.dart | 21 + lib/utils/app_error_handler.dart | 26 + .../{main_logger.dart => app_loggers.dart} | 0 .../client_network_provider.g.dart | 2 +- pubspec.lock | 16 +- pubspec.yaml | 1 + test/core/providers/prefs_provider_test.dart | 54 ++ test/core/theme/theme_provider_test.dart | 160 +++++ test/create_container.dart | 18 + test/features/schools/schools_repo_test.dart | 13 + .../language_app_provider_test.dart | 41 ++ 81 files changed, 1098 insertions(+), 252 deletions(-) create mode 100644 coverage/lcov.info rename lib/{ => core}/extensions/app_localization_extension.dart (80%) rename lib/{ => core}/extensions/context_snackbar.dart (100%) rename lib/{ => core}/extensions/hardcoded_extension.dart (100%) rename lib/{ => core}/extensions/intl_extension.dart (100%) rename lib/{ => core}/extensions/is_ios_or_macos_platform_extension.dart (100%) rename lib/{ => core}/extensions/js_bottom_padding_extension.dart (100%) rename lib/{ => core}/extensions/js_bottom_padding_extension_web.dart (100%) rename lib/{ => core}/extensions/router_extension.dart (100%) rename lib/{ => core}/extensions/string_extension.dart (100%) rename lib/{ => core}/extensions/theme_of_context_extension.dart (100%) rename lib/core/{get_native_adapter.dart => providers/client_network/network_client_adapter.dart} (100%) rename lib/core/{get_native_adapter_web.dart => providers/client_network/network_client_adapter_web.dart} (100%) rename lib/core/{ => providers}/client_network_provider.dart (90%) create mode 100644 lib/core/providers/initialization_provider.dart rename lib/{initialization_page.g.dart => core/providers/initialization_provider.g.dart} (88%) rename lib/core/{shared_preferences_provider.dart => providers/prefs_provider.dart} (62%) rename lib/{router/go_router.g.dart => core/providers/prefs_provider.g.dart} (62%) rename lib/{ => core}/theme/theme_data.dart (100%) rename lib/{ => core}/theme/theme_provider.dart (70%) rename lib/{ => core}/theme/theme_provider.g.dart (90%) rename lib/{initialization_page.dart => initialization.dart} (61%) rename lib/{router/go_router.dart => routing/app_router.dart} (90%) rename lib/{core/shared_preferences_provider.g.dart => routing/app_router.g.dart} (53%) create mode 100644 lib/routing/refresh_listenable.dart create mode 100644 lib/utils/app_error_handler.dart rename lib/utils/{main_logger.dart => app_loggers.dart} (100%) rename lib/{core => utils}/client_network_provider.g.dart (94%) create mode 100644 test/core/providers/prefs_provider_test.dart create mode 100644 test/core/theme/theme_provider_test.dart create mode 100644 test/create_container.dart create mode 100644 test/features/schools/schools_repo_test.dart create mode 100644 test/localization/language_app_provider_test.dart diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 0000000..168c085 --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,561 @@ +SF:lib/core/providers/prefs_provider.g.dart +DA:9,1 +DA:13,9 +LF:2 +LH:2 +end_of_record +SF:lib/core/providers/prefs_provider.dart +DA:6,1 +DA:8,1 +LF:2 +LH:2 +end_of_record +SF:lib/core/theme/theme_provider.dart +DA:11,1 +DA:14,5 +DA:16,3 +DA:21,1 +DA:22,4 +DA:23,2 +DA:24,1 +DA:25,2 +DA:27,1 +DA:34,1 +DA:37,5 +DA:39,1 +DA:40,1 +DA:44,3 +DA:49,1 +DA:50,1 +DA:51,1 +DA:52,4 +DA:53,1 +DA:54,1 +DA:55,3 +DA:56,1 +DA:57,1 +DA:58,1 +DA:59,4 +DA:60,1 +DA:61,1 +DA:62,1 +DA:63,4 +DA:64,1 +DA:65,1 +DA:69,1 +DA:70,4 +DA:72,1 +DA:73,1 +DA:74,1 +DA:75,1 +DA:76,1 +DA:77,1 +DA:78,1 +DA:79,1 +DA:80,1 +LF:42 +LH:42 +end_of_record +SF:lib/core/theme/theme_provider.g.dart +DA:9,0 +DA:13,3 +DA:23,1 +DA:27,2 +DA:28,1 +LF:5 +LH:4 +end_of_record +SF:lib/utils/app_loggers.dart +DA:4,0 +DA:5,3 +DA:6,0 +DA:7,0 +DA:8,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +DA:28,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:57,0 +LF:29 +LH:1 +end_of_record +SF:lib/localization/language.dart +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,1 +DA:34,1 +DA:35,0 +DA:36,0 +DA:37,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:47,0 +DA:49,0 +DA:50,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +LF:33 +LH:2 +end_of_record +SF:lib/localization/language_app_provider.dart +DA:10,1 +DA:12,4 +DA:13,1 +DA:14,4 +DA:15,1 +DA:16,3 +DA:17,0 +DA:21,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:31,0 +LF:12 +LH:6 +end_of_record +SF:lib/localization/language_app_provider.g.dart +DA:9,0 +DA:13,2 +DA:14,1 +LF:3 +LH:2 +end_of_record +SF:lib/core/extensions/app_localization_extension.dart +DA:6,0 +LF:1 +LH:0 +end_of_record +SF:lib/l10n/app_localizations.dart +DA:65,0 +DA:69,0 +DA:70,0 +DA:636,1 +DA:638,0 +DA:640,0 +DA:643,0 +DA:644,0 +DA:646,0 +DA:650,0 +DA:654,0 +DA:655,0 +DA:656,0 +DA:657,0 +DA:658,0 +DA:661,0 +LF:16 +LH:1 +end_of_record +SF:lib/l10n/app_localizations_en.dart +DA:5,0 +DA:7,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:25,0 +DA:28,0 +DA:31,0 +DA:34,0 +DA:37,0 +DA:40,0 +DA:43,0 +DA:46,0 +DA:49,0 +DA:52,0 +DA:55,0 +DA:58,0 +DA:61,0 +DA:64,0 +DA:67,0 +DA:70,0 +DA:73,0 +DA:76,0 +DA:79,0 +DA:82,0 +DA:85,0 +DA:88,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:100,0 +DA:103,0 +DA:106,0 +DA:109,0 +DA:112,0 +DA:115,0 +DA:118,0 +DA:121,0 +DA:124,0 +DA:127,0 +DA:130,0 +DA:133,0 +DA:136,0 +DA:139,0 +DA:142,0 +DA:145,0 +DA:148,0 +DA:151,0 +DA:154,0 +DA:157,0 +DA:160,0 +DA:163,0 +DA:166,0 +DA:169,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:181,0 +DA:184,0 +DA:187,0 +DA:190,0 +DA:193,0 +DA:196,0 +DA:199,0 +DA:202,0 +DA:205,0 +DA:208,0 +DA:211,0 +DA:214,0 +DA:217,0 +DA:220,0 +DA:223,0 +DA:226,0 +DA:229,0 +DA:232,0 +DA:235,0 +DA:238,0 +DA:241,0 +DA:244,0 +DA:247,0 +DA:250,0 +DA:253,0 +DA:256,0 +DA:259,0 +DA:262,0 +DA:265,0 +DA:268,0 +DA:271,0 +LF:90 +LH:0 +end_of_record +SF:lib/l10n/app_localizations_es.dart +DA:5,0 +DA:7,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:25,0 +DA:28,0 +DA:31,0 +DA:34,0 +DA:37,0 +DA:40,0 +DA:43,0 +DA:46,0 +DA:49,0 +DA:52,0 +DA:55,0 +DA:58,0 +DA:61,0 +DA:64,0 +DA:67,0 +DA:70,0 +DA:73,0 +DA:76,0 +DA:79,0 +DA:82,0 +DA:85,0 +DA:88,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:100,0 +DA:103,0 +DA:106,0 +DA:109,0 +DA:112,0 +DA:115,0 +DA:118,0 +DA:121,0 +DA:124,0 +DA:127,0 +DA:130,0 +DA:133,0 +DA:136,0 +DA:139,0 +DA:142,0 +DA:145,0 +DA:148,0 +DA:151,0 +DA:154,0 +DA:157,0 +DA:160,0 +DA:163,0 +DA:166,0 +DA:169,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:181,0 +DA:184,0 +DA:187,0 +DA:190,0 +DA:193,0 +DA:196,0 +DA:199,0 +DA:202,0 +DA:205,0 +DA:208,0 +DA:211,0 +DA:214,0 +DA:217,0 +DA:220,0 +DA:223,0 +DA:226,0 +DA:229,0 +DA:232,0 +DA:235,0 +DA:238,0 +DA:241,0 +DA:244,0 +DA:247,0 +DA:250,0 +DA:253,0 +DA:256,0 +DA:259,0 +DA:262,0 +DA:265,0 +DA:268,0 +DA:271,0 +LF:90 +LH:0 +end_of_record +SF:lib/l10n/app_localizations_ja.dart +DA:5,0 +DA:7,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:25,0 +DA:28,0 +DA:31,0 +DA:34,0 +DA:37,0 +DA:40,0 +DA:43,0 +DA:46,0 +DA:49,0 +DA:52,0 +DA:55,0 +DA:58,0 +DA:61,0 +DA:64,0 +DA:67,0 +DA:70,0 +DA:73,0 +DA:76,0 +DA:79,0 +DA:82,0 +DA:85,0 +DA:88,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:100,0 +DA:103,0 +DA:106,0 +DA:109,0 +DA:112,0 +DA:115,0 +DA:118,0 +DA:121,0 +DA:124,0 +DA:127,0 +DA:130,0 +DA:133,0 +DA:136,0 +DA:139,0 +DA:142,0 +DA:145,0 +DA:148,0 +DA:151,0 +DA:154,0 +DA:157,0 +DA:160,0 +DA:163,0 +DA:166,0 +DA:169,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:181,0 +DA:184,0 +DA:187,0 +DA:190,0 +DA:193,0 +DA:196,0 +DA:199,0 +DA:202,0 +DA:205,0 +DA:208,0 +DA:211,0 +DA:214,0 +DA:217,0 +DA:220,0 +DA:223,0 +DA:226,0 +DA:229,0 +DA:232,0 +DA:235,0 +DA:238,0 +DA:241,0 +DA:244,0 +DA:247,0 +DA:250,0 +DA:253,0 +DA:256,0 +DA:259,0 +DA:262,0 +DA:265,0 +DA:268,0 +DA:271,0 +LF:90 +LH:0 +end_of_record +SF:lib/l10n/app_localizations_pt.dart +DA:5,0 +DA:7,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:25,0 +DA:28,0 +DA:31,0 +DA:34,0 +DA:37,0 +DA:40,0 +DA:43,0 +DA:46,0 +DA:49,0 +DA:52,0 +DA:55,0 +DA:58,0 +DA:61,0 +DA:64,0 +DA:67,0 +DA:70,0 +DA:73,0 +DA:76,0 +DA:79,0 +DA:82,0 +DA:85,0 +DA:88,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:100,0 +DA:103,0 +DA:106,0 +DA:109,0 +DA:112,0 +DA:115,0 +DA:118,0 +DA:121,0 +DA:124,0 +DA:127,0 +DA:130,0 +DA:133,0 +DA:136,0 +DA:139,0 +DA:142,0 +DA:145,0 +DA:148,0 +DA:151,0 +DA:154,0 +DA:157,0 +DA:160,0 +DA:163,0 +DA:166,0 +DA:169,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:181,0 +DA:184,0 +DA:187,0 +DA:190,0 +DA:193,0 +DA:196,0 +DA:199,0 +DA:202,0 +DA:205,0 +DA:208,0 +DA:211,0 +DA:214,0 +DA:217,0 +DA:220,0 +DA:223,0 +DA:226,0 +DA:229,0 +DA:232,0 +DA:235,0 +DA:238,0 +DA:241,0 +DA:244,0 +DA:247,0 +DA:250,0 +DA:253,0 +DA:256,0 +DA:259,0 +DA:262,0 +DA:265,0 +DA:268,0 +DA:271,0 +LF:90 +LH:0 +end_of_record diff --git a/lib/common_widgets/app_async_widget.dart b/lib/common_widgets/app_async_widget.dart index 9b0a8a8..161deb8 100644 --- a/lib/common_widgets/app_async_widget.dart +++ b/lib/common_widgets/app_async_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sliver_tools/sliver_tools.dart'; -import '../extensions/app_localization_extension.dart'; +import '../core/extensions/app_localization_extension.dart'; import 'app_cupertino_button.dart'; import 'app_infinite_rotation_animation.dart'; diff --git a/lib/common_widgets/app_back_button.dart b/lib/common_widgets/app_back_button.dart index bfa1ba6..ac8a4c1 100644 --- a/lib/common_widgets/app_back_button.dart +++ b/lib/common_widgets/app_back_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../extensions/is_ios_or_macos_platform_extension.dart'; -import '../extensions/theme_of_context_extension.dart'; +import '../core/extensions/is_ios_or_macos_platform_extension.dart'; +import '../core/extensions/theme_of_context_extension.dart'; class AppBackButton extends StatelessWidget { const AppBackButton({super.key}); diff --git a/lib/common_widgets/app_cupertino_button.dart b/lib/common_widgets/app_cupertino_button.dart index e840c4a..87609ed 100644 --- a/lib/common_widgets/app_cupertino_button.dart +++ b/lib/common_widgets/app_cupertino_button.dart @@ -2,7 +2,7 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../extensions/theme_of_context_extension.dart'; +import '../core/extensions/theme_of_context_extension.dart'; ///extended CupertinoButton to pass null values in the minimumSize and padding diff --git a/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart b/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart index cf146f7..5f59334 100644 --- a/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart +++ b/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart @@ -2,10 +2,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../extensions/is_ios_or_macos_platform_extension.dart'; -import '../extensions/js_bottom_padding_extension.dart' - if (dart.library.js_interop) '../extensions/js_bottom_padding_extension_web.dart'; -import '../extensions/theme_of_context_extension.dart'; +import '../core/extensions/is_ios_or_macos_platform_extension.dart'; +import '../core/extensions/js_bottom_padding_extension.dart' + if (dart.library.js_interop) '../core/extensions/js_bottom_padding_extension_web.dart'; +import '../core/extensions/theme_of_context_extension.dart'; import '../features/home/widgets/settings_modal_sheet.dart'; import '../utils/screen_size.dart'; diff --git a/lib/common_widgets/app_fade_in_image.dart b/lib/common_widgets/app_fade_in_image.dart index db036eb..e76e8a5 100644 --- a/lib/common_widgets/app_fade_in_image.dart +++ b/lib/common_widgets/app_fade_in_image.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:transparent_image/transparent_image.dart'; -import '../extensions/app_localization_extension.dart'; -import '../extensions/theme_of_context_extension.dart'; -import '../utils/main_logger.dart'; +import '../core/extensions/app_localization_extension.dart'; +import '../core/extensions/theme_of_context_extension.dart'; +import '../utils/app_loggers.dart'; typedef ImageErrorWidgetBuilder = Widget Function( BuildContext context, diff --git a/lib/common_widgets/app_web_padding.dart b/lib/common_widgets/app_web_padding.dart index 1ce47b0..fa552d4 100644 --- a/lib/common_widgets/app_web_padding.dart +++ b/lib/common_widgets/app_web_padding.dart @@ -2,8 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../extensions/js_bottom_padding_extension.dart' - if (dart.library.js_interop) '../extensions/js_bottom_padding_extension_web.dart'; +import '../core/extensions/js_bottom_padding_extension.dart' + if (dart.library.js_interop) '../core/extensions/js_bottom_padding_extension.dart'; /// Widget that calls the calls to JS to get the insets of web /// https://github.com/flutter/flutter/issues/84833#issuecomment-1679737846 diff --git a/lib/extensions/app_localization_extension.dart b/lib/core/extensions/app_localization_extension.dart similarity index 80% rename from lib/extensions/app_localization_extension.dart rename to lib/core/extensions/app_localization_extension.dart index 7224b6e..23aeab6 100644 --- a/lib/extensions/app_localization_extension.dart +++ b/lib/core/extensions/app_localization_extension.dart @@ -1,6 +1,6 @@ // app_localizations_context.dart import 'package:flutter/widgets.dart'; -import '../l10n/app_localizations.dart'; +import '../../l10n/app_localizations.dart'; extension LocalizedBuildContext on BuildContext { AppLocalizations get loc => AppLocalizations.of(this); diff --git a/lib/extensions/context_snackbar.dart b/lib/core/extensions/context_snackbar.dart similarity index 100% rename from lib/extensions/context_snackbar.dart rename to lib/core/extensions/context_snackbar.dart diff --git a/lib/extensions/hardcoded_extension.dart b/lib/core/extensions/hardcoded_extension.dart similarity index 100% rename from lib/extensions/hardcoded_extension.dart rename to lib/core/extensions/hardcoded_extension.dart diff --git a/lib/extensions/intl_extension.dart b/lib/core/extensions/intl_extension.dart similarity index 100% rename from lib/extensions/intl_extension.dart rename to lib/core/extensions/intl_extension.dart diff --git a/lib/extensions/is_ios_or_macos_platform_extension.dart b/lib/core/extensions/is_ios_or_macos_platform_extension.dart similarity index 100% rename from lib/extensions/is_ios_or_macos_platform_extension.dart rename to lib/core/extensions/is_ios_or_macos_platform_extension.dart diff --git a/lib/extensions/js_bottom_padding_extension.dart b/lib/core/extensions/js_bottom_padding_extension.dart similarity index 100% rename from lib/extensions/js_bottom_padding_extension.dart rename to lib/core/extensions/js_bottom_padding_extension.dart diff --git a/lib/extensions/js_bottom_padding_extension_web.dart b/lib/core/extensions/js_bottom_padding_extension_web.dart similarity index 100% rename from lib/extensions/js_bottom_padding_extension_web.dart rename to lib/core/extensions/js_bottom_padding_extension_web.dart diff --git a/lib/extensions/router_extension.dart b/lib/core/extensions/router_extension.dart similarity index 100% rename from lib/extensions/router_extension.dart rename to lib/core/extensions/router_extension.dart diff --git a/lib/extensions/string_extension.dart b/lib/core/extensions/string_extension.dart similarity index 100% rename from lib/extensions/string_extension.dart rename to lib/core/extensions/string_extension.dart diff --git a/lib/extensions/theme_of_context_extension.dart b/lib/core/extensions/theme_of_context_extension.dart similarity index 100% rename from lib/extensions/theme_of_context_extension.dart rename to lib/core/extensions/theme_of_context_extension.dart diff --git a/lib/core/get_native_adapter.dart b/lib/core/providers/client_network/network_client_adapter.dart similarity index 100% rename from lib/core/get_native_adapter.dart rename to lib/core/providers/client_network/network_client_adapter.dart diff --git a/lib/core/get_native_adapter_web.dart b/lib/core/providers/client_network/network_client_adapter_web.dart similarity index 100% rename from lib/core/get_native_adapter_web.dart rename to lib/core/providers/client_network/network_client_adapter_web.dart diff --git a/lib/core/client_network_provider.dart b/lib/core/providers/client_network_provider.dart similarity index 90% rename from lib/core/client_network_provider.dart rename to lib/core/providers/client_network_provider.dart index 38c8af4..da645a5 100644 --- a/lib/core/client_network_provider.dart +++ b/lib/core/providers/client_network_provider.dart @@ -6,13 +6,13 @@ import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_stor import 'package:path_provider/path_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../localization/language.dart'; -import '../localization/language_app_provider.dart'; -import '../utils/main_logger.dart'; -import 'get_native_adapter.dart' - if (dart.library.js_interop) 'get_native_adapter_web.dart'; +import '../../localization/language.dart'; +import '../../localization/language_app_provider.dart'; +import '../../utils/app_loggers.dart'; +import 'client_network/network_client_adapter.dart' + if (dart.library.js_interop) 'client_network/network_client_adapter_web.dart'; -part 'client_network_provider.g.dart'; +part '../../utils/client_network_provider.g.dart'; const _baseUrlPath = 'https://samba.deno.dev'; const _connectTimeout = Duration(seconds: 2); diff --git a/lib/core/providers/initialization_provider.dart b/lib/core/providers/initialization_provider.dart new file mode 100644 index 0000000..452bdab --- /dev/null +++ b/lib/core/providers/initialization_provider.dart @@ -0,0 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils/app_error_handler.dart'; +import '../../utils/app_loggers.dart'; +import '../../utils/immutable_list.dart'; +import 'prefs_provider.dart'; + +part 'initialization_provider.g.dart'; + +@Riverpod(keepAlive: true) +Future initialization(InitializationRef ref) async { + registerErrorHandlers(); + initializeFICMappers(); + if (kDebugMode) initLoggers(Level.FINE, {}); + ref.onDispose(() { + ref.invalidate(prefsProvider); + }); + await ref.watch(prefsProvider.future); +} diff --git a/lib/initialization_page.g.dart b/lib/core/providers/initialization_provider.g.dart similarity index 88% rename from lib/initialization_page.g.dart rename to lib/core/providers/initialization_provider.g.dart index 03ea771..399831e 100644 --- a/lib/initialization_page.g.dart +++ b/lib/core/providers/initialization_provider.g.dart @@ -1,12 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'initialization_page.dart'; +part of 'initialization_provider.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$initializationHash() => r'4d1c86ca1ab823a045818e9ecd8f37380d3fbbfe'; +String _$initializationHash() => r'c3e3a01b85067c9aeb106e4564cd04295fd6642a'; /// See also [initialization]. @ProviderFor(initialization) diff --git a/lib/core/shared_preferences_provider.dart b/lib/core/providers/prefs_provider.dart similarity index 62% rename from lib/core/shared_preferences_provider.dart rename to lib/core/providers/prefs_provider.dart index 6fff40e..00da851 100644 --- a/lib/core/shared_preferences_provider.dart +++ b/lib/core/providers/prefs_provider.dart @@ -1,8 +1,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; -part 'shared_preferences_provider.g.dart'; +part 'prefs_provider.g.dart'; @Riverpod(keepAlive: true) -Future sharedPreferences(SharedPreferencesRef ref) => +Future prefs(PrefsRef ref) => SharedPreferences.getInstance(); diff --git a/lib/router/go_router.g.dart b/lib/core/providers/prefs_provider.g.dart similarity index 62% rename from lib/router/go_router.g.dart rename to lib/core/providers/prefs_provider.g.dart index a55830b..d9d911d 100644 --- a/lib/router/go_router.g.dart +++ b/lib/core/providers/prefs_provider.g.dart @@ -1,24 +1,24 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'go_router.dart'; +part of 'prefs_provider.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$goRouterHash() => r'3c17b1afc4de4f7b29a826183ea2903eb57b40e0'; +String _$prefsHash() => r'e8538273305ad075d4c7d66aa5893f862d51be3f'; -/// See also [goRouter]. -@ProviderFor(goRouter) -final goRouterProvider = AutoDisposeProvider.internal( - goRouter, - name: r'goRouterProvider', +/// See also [prefs]. +@ProviderFor(prefs) +final prefsProvider = FutureProvider.internal( + prefs, + name: r'prefsProvider', debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$goRouterHash, + const bool.fromEnvironment('dart.vm.product') ? null : _$prefsHash, dependencies: null, allTransitiveDependencies: null, ); -typedef GoRouterRef = AutoDisposeProviderRef; +typedef PrefsRef = FutureProviderRef; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/theme/theme_data.dart b/lib/core/theme/theme_data.dart similarity index 100% rename from lib/theme/theme_data.dart rename to lib/core/theme/theme_data.dart diff --git a/lib/theme/theme_provider.dart b/lib/core/theme/theme_provider.dart similarity index 70% rename from lib/theme/theme_provider.dart rename to lib/core/theme/theme_provider.dart index 673c1ad..86dab3a 100644 --- a/lib/theme/theme_provider.dart +++ b/lib/core/theme/theme_provider.dart @@ -1,21 +1,44 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../core/shared_preferences_provider.dart'; -import '../utils/main_logger.dart'; + +import '../../utils/app_loggers.dart'; +import '../providers/prefs_provider.dart'; part 'theme_provider.g.dart'; +@riverpod +class AppThemeTrueBlack extends _$AppThemeTrueBlack { + @override + bool build() { + try { + return ref.watch(prefsProvider).value!.getBool('true_black') ?? false; + } catch (e) { + logViews.finest('$e'); + return false; + } + } + + void toggleTrueBlack({bool? forceState}) { + final prefs = ref.read(prefsProvider).requireValue; + state = forceState ?? !state; + if (state) { + prefs.setBool('true_black', state); + } else { + prefs.remove('true_black'); + } + } +} + @Riverpod(keepAlive: true) class AppThemeMode extends _$AppThemeMode { @override ThemeMode build() { try { - final mode = - ref.watch(sharedPreferencesProvider).value!.getString('theme_mode'); + final mode = ref.watch(prefsProvider).value!.getString('theme_mode'); return switch (mode) { 'light' => ThemeMode.light, 'dark' => ThemeMode.dark, - 'system' || (_) => ThemeMode.system, + (_) => ThemeMode.system, }; } catch (e) { logViews.finest('$e'); @@ -26,25 +49,25 @@ class AppThemeMode extends _$AppThemeMode { Future toggleTheme() async { switch (state) { case ThemeMode.system: - final prefs = ref.read(sharedPreferencesProvider).requireValue; + final prefs = ref.read(prefsProvider).requireValue; await prefs.setString('theme_mode', 'light'); ref .read(appThemeTrueBlackProvider.notifier) .toggleTrueBlack(forceState: false); state = ThemeMode.light; case ThemeMode.light: - final prefs = ref.read(sharedPreferencesProvider).requireValue; + final prefs = ref.read(prefsProvider).requireValue; await prefs.setString('theme_mode', 'dark'); state = ThemeMode.dark; case ThemeMode.dark: - final prefs = ref.read(sharedPreferencesProvider).requireValue; + final prefs = ref.read(prefsProvider).requireValue; await prefs.remove('theme_mode'); state = ThemeMode.system; } } void setTheme(ThemeMode mode) { - final prefs = ref.read(sharedPreferencesProvider).requireValue; + final prefs = ref.read(prefsProvider).requireValue; switch (mode) { case ThemeMode.system: prefs.remove('theme_mode'); @@ -58,30 +81,3 @@ class AppThemeMode extends _$AppThemeMode { } } } - -@riverpod -class AppThemeTrueBlack extends _$AppThemeTrueBlack { - @override - bool build() { - try { - return ref - .watch(sharedPreferencesProvider) - .value! - .getBool('true_black') ?? - false; - } catch (e) { - logViews.finest('$e'); - return false; - } - } - - void toggleTrueBlack({bool? forceState}) { - final prefs = ref.read(sharedPreferencesProvider).requireValue; - state = forceState ?? !state; - if (state) { - prefs.setBool('true_black', state); - } else { - prefs.remove('true_black'); - } - } -} diff --git a/lib/theme/theme_provider.g.dart b/lib/core/theme/theme_provider.g.dart similarity index 90% rename from lib/theme/theme_provider.g.dart rename to lib/core/theme/theme_provider.g.dart index eb9d5fa..2f98a20 100644 --- a/lib/theme/theme_provider.g.dart +++ b/lib/core/theme/theme_provider.g.dart @@ -6,7 +6,7 @@ part of 'theme_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$appThemeModeHash() => r'429f7a40d1df8304a535214708cfb66f66f08773'; +String _$appThemeModeHash() => r'b0bcd77f3e7da8c63dfa315a5a07c720309d6bc9'; /// See also [AppThemeMode]. @ProviderFor(AppThemeMode) @@ -20,7 +20,7 @@ final appThemeModeProvider = NotifierProvider.internal( ); typedef _$AppThemeMode = Notifier; -String _$appThemeTrueBlackHash() => r'c71eaecafef02c241b4e8c996cde320440828fe2'; +String _$appThemeTrueBlackHash() => r'45e4f39626cbefaed196f5d2a35c786af909cab9'; /// See also [AppThemeTrueBlack]. @ProviderFor(AppThemeTrueBlack) diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart index fde8ae1..adee111 100644 --- a/lib/features/home/home_page.dart +++ b/lib/features/home/home_page.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../common_widgets/app_web_padding.dart'; -import '../../extensions/theme_of_context_extension.dart'; +import '../../core/extensions/theme_of_context_extension.dart'; import '../../utils/immutable_list.dart'; import '../../utils/screen_size.dart'; import 'home_page_controller.dart'; diff --git a/lib/features/home/home_page_controller.dart b/lib/features/home/home_page_controller.dart index f8fadf8..1e38e1f 100644 --- a/lib/features/home/home_page_controller.dart +++ b/lib/features/home/home_page_controller.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../extensions/app_localization_extension.dart'; +import '../../core/extensions/app_localization_extension.dart'; import '../instruments/instruments_tab_page.dart'; import '../parades/parades_tab_page.dart'; import '../schools/schools_tab_page.dart'; diff --git a/lib/features/home/widgets/adaptive_navigation_bar.dart b/lib/features/home/widgets/adaptive_navigation_bar.dart index 1143361..411b91a 100644 --- a/lib/features/home/widgets/adaptive_navigation_bar.dart +++ b/lib/features/home/widgets/adaptive_navigation_bar.dart @@ -2,8 +2,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../../../common_widgets/app_web_padding.dart'; -import '../../../extensions/is_ios_or_macos_platform_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/is_ios_or_macos_platform_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/immutable_list.dart'; import '../home_page_controller.dart'; diff --git a/lib/features/home/widgets/adaptive_navigation_rail.dart b/lib/features/home/widgets/adaptive_navigation_rail.dart index 7117459..89dd1c2 100644 --- a/lib/features/home/widgets/adaptive_navigation_rail.dart +++ b/lib/features/home/widgets/adaptive_navigation_rail.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/immutable_list.dart'; import '../../../utils/screen_size.dart'; import '../home_page_controller.dart'; diff --git a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart index 5268e65..86c5dcf 100644 --- a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart +++ b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart @@ -3,11 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pull_down_button/pull_down_button.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; +import '../../../core/theme/theme_provider.dart'; import '../../../localization/language.dart'; import '../../../localization/language_app_provider.dart'; -import '../../../theme/theme_provider.dart'; import '../../../utils/screen_size.dart'; import 'settings_modal_sheet.dart'; diff --git a/lib/features/home/widgets/settings_modal_sheet.dart b/lib/features/home/widgets/settings_modal_sheet.dart index e981c3f..7cdd6b8 100644 --- a/lib/features/home/widgets/settings_modal_sheet.dart +++ b/lib/features/home/widgets/settings_modal_sheet.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../common_widgets/app_cupertino_button.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../localization/language.dart'; import '../../../localization/language_app_provider.dart'; import 'settings_theme_section.dart'; diff --git a/lib/features/home/widgets/settings_theme_section.dart b/lib/features/home/widgets/settings_theme_section.dart index fa17c0a..6681928 100644 --- a/lib/features/home/widgets/settings_theme_section.dart +++ b/lib/features/home/widgets/settings_theme_section.dart @@ -1,9 +1,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; -import '../../../theme/theme_provider.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; +import '../../../core/theme/theme_provider.dart'; class SettingsThemeSection extends ConsumerWidget { const SettingsThemeSection({super.key}); diff --git a/lib/features/instruments/details/instrument_details_page.dart b/lib/features/instruments/details/instrument_details_page.dart index 407d32c..34caa2a 100644 --- a/lib/features/instruments/details/instrument_details_page.dart +++ b/lib/features/instruments/details/instrument_details_page.dart @@ -5,7 +5,7 @@ import 'package:sliver_tools/sliver_tools.dart'; import '../../../common_widgets/app_back_button.dart'; import '../../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; import '../../../common_widgets/app_web_padding.dart'; -import '../../../extensions/app_localization_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; import '../../../utils/screen_size.dart'; import 'instrument_details_providers.dart'; import 'widgets/instrument_details_summary.dart'; diff --git a/lib/features/instruments/details/widgets/instrument_details_learning.dart b/lib/features/instruments/details/widgets/instrument_details_learning.dart index e8ecfdb..f027efe 100644 --- a/lib/features/instruments/details/widgets/instrument_details_learning.dart +++ b/lib/features/instruments/details/widgets/instrument_details_learning.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../../../extensions/hardcoded_extension.dart'; +import '../../../../core/extensions/hardcoded_extension.dart'; class InstrumentDetailsLearning extends StatelessWidget { const InstrumentDetailsLearning({super.key}); diff --git a/lib/features/instruments/details/widgets/instrument_header_images.dart b/lib/features/instruments/details/widgets/instrument_header_images.dart index a3fcf44..69addc9 100644 --- a/lib/features/instruments/details/widgets/instrument_header_images.dart +++ b/lib/features/instruments/details/widgets/instrument_header_images.dart @@ -3,7 +3,7 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import '../../../../common_widgets/app_fade_in_image.dart'; -import '../../../../extensions/theme_of_context_extension.dart'; +import '../../../../core/extensions/theme_of_context_extension.dart'; import '../../../../utils/immutable_list.dart'; import '../../../../utils/screen_size.dart'; import '../../instrument.dart'; diff --git a/lib/features/instruments/instruments_repo.dart b/lib/features/instruments/instruments_repo.dart index 8252065..4a61391 100644 --- a/lib/features/instruments/instruments_repo.dart +++ b/lib/features/instruments/instruments_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/client_network_provider.dart'; +import '../../core/providers/client_network_provider.dart'; import '../../utils/immutable_list.dart'; import 'details/instrument_details_page.dart'; import 'instrument.dart'; diff --git a/lib/features/instruments/instruments_tab_page.dart b/lib/features/instruments/instruments_tab_page.dart index acd2cb4..a9b25b1 100644 --- a/lib/features/instruments/instruments_tab_page.dart +++ b/lib/features/instruments/instruments_tab_page.dart @@ -7,7 +7,7 @@ import 'package:sliver_tools/sliver_tools.dart'; import '../../common_widgets/app_async_widget.dart'; import '../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; import '../../common_widgets/app_web_padding.dart'; -import '../../extensions/app_localization_extension.dart'; +import '../../core/extensions/app_localization_extension.dart'; import '../../utils/screen_size.dart'; import '../home/home_page_controller.dart'; import 'instruments_tab_providers.dart'; diff --git a/lib/features/instruments/widgets/instrument_list_tile.dart b/lib/features/instruments/widgets/instrument_list_tile.dart index 4997e66..586cba0 100644 --- a/lib/features/instruments/widgets/instrument_list_tile.dart +++ b/lib/features/instruments/widgets/instrument_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import '../../../common_widgets/app_fade_in_image.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; typedef ImageUrl = String; diff --git a/lib/features/parades/parade_extension.dart b/lib/features/parades/parade_extension.dart index 63738a4..91572e0 100644 --- a/lib/features/parades/parade_extension.dart +++ b/lib/features/parades/parade_extension.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../extensions/theme_of_context_extension.dart'; +import '../../core/extensions/theme_of_context_extension.dart'; import '../schools/school.dart'; import 'parade.dart'; diff --git a/lib/features/parades/parades_repo.dart b/lib/features/parades/parades_repo.dart index 0517bd6..d070fe3 100644 --- a/lib/features/parades/parades_repo.dart +++ b/lib/features/parades/parades_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/client_network_provider.dart'; +import '../../core/providers/client_network_provider.dart'; import '../../utils/immutable_list.dart'; import 'parade.dart'; diff --git a/lib/features/parades/parades_tab_page.dart b/lib/features/parades/parades_tab_page.dart index a54bded..8660bcb 100644 --- a/lib/features/parades/parades_tab_page.dart +++ b/lib/features/parades/parades_tab_page.dart @@ -7,7 +7,7 @@ import '../../common_widgets/app_animation_wrapper.dart'; import '../../common_widgets/app_async_widget.dart'; import '../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; import '../../common_widgets/app_loading_indicator.dart'; -import '../../extensions/app_localization_extension.dart'; +import '../../core/extensions/app_localization_extension.dart'; import '../../utils/debouncer.dart'; import '../../utils/screen_size.dart'; import '../home/home_page_controller.dart'; diff --git a/lib/features/parades/parades_tab_providers.dart b/lib/features/parades/parades_tab_providers.dart index c68ba98..2999ccf 100644 --- a/lib/features/parades/parades_tab_providers.dart +++ b/lib/features/parades/parades_tab_providers.dart @@ -1,7 +1,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; -import '../../utils/main_logger.dart'; import 'parade.dart'; import 'parades_repo.dart'; diff --git a/lib/features/parades/parades_tab_providers.g.dart b/lib/features/parades/parades_tab_providers.g.dart index 8d2a7d6..bf118ed 100644 --- a/lib/features/parades/parades_tab_providers.g.dart +++ b/lib/features/parades/parades_tab_providers.g.dart @@ -6,7 +6,7 @@ part of 'parades_tab_providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$paradesHash() => r'c49a4e922bbd7721ea995b6fd49483dbffd81cea'; +String _$paradesHash() => r'212ae8bb76bd4971f4aa3e384283957fe8dd780c'; /// See also [Parades]. @ProviderFor(Parades) diff --git a/lib/features/parades/widgets/parade_item.dart b/lib/features/parades/widgets/parade_item.dart index b72991b..96ea923 100644 --- a/lib/features/parades/widgets/parade_item.dart +++ b/lib/features/parades/widgets/parade_item.dart @@ -5,8 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../common_widgets/app_animated_linear_gradient.dart'; import '../../../common_widgets/app_fade_in_image.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../schools/school_extensions.dart'; import '../parade.dart'; import '../parade_extension.dart'; diff --git a/lib/features/parades/widgets/parade_item_bottom_row.dart b/lib/features/parades/widgets/parade_item_bottom_row.dart index 48efd48..25ab49c 100644 --- a/lib/features/parades/widgets/parade_item_bottom_row.dart +++ b/lib/features/parades/widgets/parade_item_bottom_row.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import '../../../common_widgets/app_animated_linear_gradient.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/intl_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/intl_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../parade.dart'; import '../parade_extension.dart'; diff --git a/lib/features/parades/widgets/parade_item_sidebar.dart b/lib/features/parades/widgets/parade_item_sidebar.dart index 3fe9532..d2d54b9 100644 --- a/lib/features/parades/widgets/parade_item_sidebar.dart +++ b/lib/features/parades/widgets/parade_item_sidebar.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/intl_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/intl_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../parade.dart'; import '../parade_extension.dart'; diff --git a/lib/features/parades/widgets/parade_item_year_line.dart b/lib/features/parades/widgets/parade_item_year_line.dart index e2a2e93..f7544f4 100644 --- a/lib/features/parades/widgets/parade_item_year_line.dart +++ b/lib/features/parades/widgets/parade_item_year_line.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; class ParadeItemYearLine extends StatelessWidget { const ParadeItemYearLine({ diff --git a/lib/features/schools/details/school_details_page.dart b/lib/features/schools/details/school_details_page.dart index 93673ed..3a5a99a 100644 --- a/lib/features/schools/details/school_details_page.dart +++ b/lib/features/schools/details/school_details_page.dart @@ -4,10 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../common_widgets/app_cupertino_button.dart'; import '../../../common_widgets/app_page_indicator.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/intl_extension.dart'; -import '../../../extensions/string_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/intl_extension.dart'; +import '../../../core/extensions/string_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../school.dart'; import '../school_extensions.dart'; import '../widgets/school_flag.dart'; diff --git a/lib/features/schools/school.dart b/lib/features/schools/school.dart index a1aab80..4faac3a 100644 --- a/lib/features/schools/school.dart +++ b/lib/features/schools/school.dart @@ -1,7 +1,7 @@ import 'package:dart_mappable/dart_mappable.dart'; import 'package:flutter/material.dart'; -import '../../extensions/app_localization_extension.dart'; +import '../../core/extensions/app_localization_extension.dart'; import '../../utils/immutable_list.dart'; import 'school_color_hook.dart'; diff --git a/lib/features/schools/school_color_hook.dart b/lib/features/schools/school_color_hook.dart index 15426f4..15e39c8 100644 --- a/lib/features/schools/school_color_hook.dart +++ b/lib/features/schools/school_color_hook.dart @@ -1,8 +1,8 @@ import 'package:dart_mappable/dart_mappable.dart'; import 'package:flutter/material.dart'; +import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; -import '../../utils/main_logger.dart'; class ColorHook extends MappingHook { const ColorHook(); diff --git a/lib/features/schools/school_extensions.dart b/lib/features/schools/school_extensions.dart index 45c894a..96b9baa 100644 --- a/lib/features/schools/school_extensions.dart +++ b/lib/features/schools/school_extensions.dart @@ -2,8 +2,8 @@ library; import 'package:flutter/material.dart'; -import '../../extensions/app_localization_extension.dart'; -import '../../extensions/string_extension.dart'; +import '../../core/extensions/app_localization_extension.dart'; +import '../../core/extensions/string_extension.dart'; import 'school.dart'; extension SchoolDivisionExtension on SchoolDivision { diff --git a/lib/features/schools/schools_repo.dart b/lib/features/schools/schools_repo.dart index 8e8eac0..cee4b83 100644 --- a/lib/features/schools/schools_repo.dart +++ b/lib/features/schools/schools_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/client_network_provider.dart'; +import '../../core/providers/client_network_provider.dart'; import '../../utils/immutable_list.dart'; import 'school.dart'; diff --git a/lib/features/schools/schools_tab_providers.dart b/lib/features/schools/schools_tab_providers.dart index d905165..42e8747 100644 --- a/lib/features/schools/schools_tab_providers.dart +++ b/lib/features/schools/schools_tab_providers.dart @@ -1,8 +1,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/shared_preferences_provider.dart'; +import '../../core/providers/prefs_provider.dart'; +import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; -import '../../utils/main_logger.dart'; import 'school.dart'; import 'schools_repo.dart'; import 'widgets/school_card.dart'; @@ -66,12 +66,12 @@ class Schools extends _$Schools { class FavoriteSchools extends _$FavoriteSchools { @override ImmutableList build() { - final prefs = ref.watch(sharedPreferencesProvider).value!; + final prefs = ref.watch(prefsProvider).value!; return ImmutableList(prefs.getStringList('favoriteSchools') ?? []); } void toggleFavorite(SchoolId id) { - final prefs = ref.watch(sharedPreferencesProvider).value!; + final prefs = ref.watch(prefsProvider).value!; final favoriteSchools = prefs.getStringList('favoriteSchools') ?? []; if (favoriteSchools.contains('$id')) { favoriteSchools.remove('$id'); diff --git a/lib/features/schools/schools_tab_providers.g.dart b/lib/features/schools/schools_tab_providers.g.dart index b651ba5..56b5ab5 100644 --- a/lib/features/schools/schools_tab_providers.g.dart +++ b/lib/features/schools/schools_tab_providers.g.dart @@ -6,7 +6,7 @@ part of 'schools_tab_providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$schoolsHash() => r'086f0ba5dcd4a1dd3d0df75ac87caf1005817b44'; +String _$schoolsHash() => r'4aba4f933e856cc38139fc9d9b77abbb5496fbeb'; /// See also [Schools]. @ProviderFor(Schools) @@ -21,7 +21,7 @@ final schoolsProvider = ); typedef _$Schools = AutoDisposeAsyncNotifier>; -String _$favoriteSchoolsHash() => r'2734a25c2df1d78c93a6853617a63d8f0d28f1a3'; +String _$favoriteSchoolsHash() => r'8acf3025d87137d207d0871badbc5b455f90d8c6'; /// See also [FavoriteSchools]. @ProviderFor(FavoriteSchools) @@ -37,7 +37,7 @@ final favoriteSchoolsProvider = AutoDisposeNotifierProvider>; -String _$schoolDivisionsHash() => r'790ca8cc34132ada189c3bdfaa8f3e68046700f0'; +String _$schoolDivisionsHash() => r'fd0bd47cb45eff1499fdb954f54778f2b22f4347'; /// See also [SchoolDivisions]. @ProviderFor(SchoolDivisions) diff --git a/lib/features/schools/widgets/school_card.dart b/lib/features/schools/widgets/school_card.dart index e0a28c1..52baaf6 100644 --- a/lib/features/schools/widgets/school_card.dart +++ b/lib/features/schools/widgets/school_card.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/intl_extension.dart'; -import '../../../extensions/string_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/intl_extension.dart'; +import '../../../core/extensions/string_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../school.dart'; import '../school_extensions.dart'; diff --git a/lib/features/schools/widgets/school_filter_chips.dart b/lib/features/schools/widgets/school_filter_chips.dart index 00afe00..d716271 100644 --- a/lib/features/schools/widgets/school_filter_chips.dart +++ b/lib/features/schools/widgets/school_filter_chips.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sliver_tools/sliver_tools.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../school_extensions.dart'; import '../schools_tab_providers.dart'; diff --git a/lib/features/schools/widgets/school_flag.dart b/lib/features/schools/widgets/school_flag.dart index 8b3f7f3..bc57e45 100644 --- a/lib/features/schools/widgets/school_flag.dart +++ b/lib/features/schools/widgets/school_flag.dart @@ -3,8 +3,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../common_widgets/app_fade_in_image.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../school.dart'; import '../schools_tab_providers.dart'; diff --git a/lib/features/schools/widgets/schools_empty_list.dart b/lib/features/schools/widgets/schools_empty_list.dart index f044d38..20cecdd 100644 --- a/lib/features/schools/widgets/schools_empty_list.dart +++ b/lib/features/schools/widgets/schools_empty_list.dart @@ -1,8 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../schools_tab_providers.dart'; class SchoolsEmptyList extends ConsumerWidget { diff --git a/lib/features/schools/widgets/schools_tab_body.dart b/lib/features/schools/widgets/schools_tab_body.dart index 1d1c844..6a6ccb2 100644 --- a/lib/features/schools/widgets/schools_tab_body.dart +++ b/lib/features/schools/widgets/schools_tab_body.dart @@ -5,8 +5,8 @@ import 'package:sliver_tools/sliver_tools.dart'; import '../../../common_widgets/app_animation_wrapper.dart'; import '../../../common_widgets/app_async_widget.dart'; -import '../../../extensions/js_bottom_padding_extension.dart' - if (dart.library.js_interop) '../../../extensions/js_bottom_padding_extension_web.dart'; +import '../../../core/extensions/js_bottom_padding_extension.dart' + if (dart.library.js_interop) '../../../core/extensions/js_bottom_padding_extension_web.dart'; import '../../../utils/immutable_list.dart'; import '../../../utils/screen_size.dart'; import '../school.dart'; diff --git a/lib/features/schools/widgets/schools_tab_navbar.dart b/lib/features/schools/widgets/schools_tab_navbar.dart index 5b828d2..4fb7195 100644 --- a/lib/features/schools/widgets/schools_tab_navbar.dart +++ b/lib/features/schools/widgets/schools_tab_navbar.dart @@ -4,8 +4,8 @@ import 'package:sliver_tools/sliver_tools.dart'; import '../../../common_widgets/app_cupertino_button.dart'; import '../../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/hardcoded_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/hardcoded_extension.dart'; import '../../../utils/screen_size.dart'; class SchoolsTabNavBar extends StatelessWidget { diff --git a/lib/features/schools/widgets/schools_tab_search_header.dart b/lib/features/schools/widgets/schools_tab_search_header.dart index d8ef297..b4763c5 100644 --- a/lib/features/schools/widgets/schools_tab_search_header.dart +++ b/lib/features/schools/widgets/schools_tab_search_header.dart @@ -5,8 +5,8 @@ import 'package:sliver_tools/sliver_tools.dart'; import '../../../common_widgets/app_cupertino_button.dart'; import '../../../common_widgets/app_web_padding.dart'; -import '../../../extensions/app_localization_extension.dart'; -import '../../../extensions/theme_of_context_extension.dart'; +import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../../home/widgets/adaptive_navigation_rail.dart'; import '../school.dart'; diff --git a/lib/initialization_page.dart b/lib/initialization.dart similarity index 61% rename from lib/initialization_page.dart rename to lib/initialization.dart index c66c3dd..71a9d7f 100644 --- a/lib/initialization_page.dart +++ b/lib/initialization.dart @@ -1,55 +1,13 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:logging/logging.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'core/shared_preferences_provider.dart'; -import 'extensions/app_localization_extension.dart'; +import 'core/extensions/app_localization_extension.dart'; +import 'core/providers/initialization_provider.dart'; +import 'core/theme/theme_provider.dart'; import 'features/home/widgets/adaptive_navigation_rail.dart'; -import 'theme/theme_provider.dart'; -import 'utils/immutable_list.dart'; -import 'utils/main_logger.dart'; import 'utils/screen_size.dart'; -part 'initialization_page.g.dart'; - -@Riverpod(keepAlive: true) -Future initialization(InitializationRef ref) async { - registerErrorHandlers(); - initializeFICMappers(); - if (kDebugMode) initLoggers(Level.FINE, {}); - ref.onDispose(() { - ref.invalidate(sharedPreferencesProvider); - }); - await ref.watch(sharedPreferencesProvider.future); -} - -void registerErrorHandlers() { - // * Show some error UI if any uncaught exception happens - FlutterError.onError = (FlutterErrorDetails details) { - FlutterError.presentError(details); - debugPrint(details.toString()); - }; - // * Handle errors from the underlying platform/OS - PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { - debugPrint(error.toString()); - return true; - }; - // * Show some error UI when any widget in the app fails to build - ErrorWidget.builder = (FlutterErrorDetails details) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.red, - title: const Text('An error occurred'), - ), - body: Center(child: Text(details.toString())), - ); - }; -} - -/// Widget class to manage asynchronous app initialization class InitializationPage extends ConsumerWidget { const InitializationPage({required this.onLoaded, super.key}); final WidgetBuilder onLoaded; @@ -58,9 +16,8 @@ class InitializationPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final initProvider = ref.watch(initializationProvider); ref.watch(appThemeModeProvider); - + final initProvider = ref.watch(initializationProvider); return AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: switch (initProvider) { @@ -83,12 +40,11 @@ class AppStartupLoadingWidget extends StatelessWidget { @override Widget build(BuildContext context) { final screenSize = context.screenSize; - final padding = MediaQuery.paddingOf(context); return Scaffold( appBar: AppBar( elevation: 0, - toolbarHeight: - kMinInteractiveDimensionCupertino + padding.top.clamp(52, 100), + toolbarHeight: kMinInteractiveDimensionCupertino + + MediaQuery.paddingOf(context).top.clamp(52, 100), ), bottomNavigationBar: screenSize.isSmall ? null diff --git a/lib/localization/language.dart b/lib/localization/language.dart index 988fd20..b907c33 100644 --- a/lib/localization/language.dart +++ b/lib/localization/language.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../extensions/app_localization_extension.dart'; +import '../core/extensions/app_localization_extension.dart'; enum Language { en, diff --git a/lib/localization/language_app_provider.dart b/lib/localization/language_app_provider.dart index 99f2731..60e4c33 100644 --- a/lib/localization/language_app_provider.dart +++ b/lib/localization/language_app_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../core/shared_preferences_provider.dart'; +import '../core/providers/prefs_provider.dart'; import 'language.dart'; part 'language_app_provider.g.dart'; @@ -9,7 +9,7 @@ part 'language_app_provider.g.dart'; class LanguageApp extends _$LanguageApp { @override FutureOr build() async { - final prefs = await ref.watch(sharedPreferencesProvider.future); + final prefs = await ref.watch(prefsProvider.future); final localeKey = prefs.getString('locale') ?? WidgetsBinding.instance.platformDispatcher.locale.languageCode; return Language.values.firstWhere( @@ -22,7 +22,7 @@ class LanguageApp extends _$LanguageApp { Language language, { required bool isSameAsPlatform, }) async { - final prefs = ref.read(sharedPreferencesProvider).value; + final prefs = ref.read(prefsProvider).value; if (isSameAsPlatform) { await prefs!.remove('locale'); } else { diff --git a/lib/localization/language_app_provider.g.dart b/lib/localization/language_app_provider.g.dart index 09d93a2..4687bff 100644 --- a/lib/localization/language_app_provider.g.dart +++ b/lib/localization/language_app_provider.g.dart @@ -6,7 +6,7 @@ part of 'language_app_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$languageAppHash() => r'e30aa5ff94a3c5293ac17e9904bc63cab026eb1e'; +String _$languageAppHash() => r'2949ce52a24ff04ecfdc4428033b35b6f27170c9'; /// See also [LanguageApp]. @ProviderFor(LanguageApp) diff --git a/lib/main.dart b/lib/main.dart index 8a015dd..6b97619 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:country_picker/country_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -6,47 +7,38 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; // ignore:depend_on_referenced_packages import 'package:flutter_web_plugins/url_strategy.dart'; -import 'extensions/app_localization_extension.dart'; +import 'core/extensions/app_localization_extension.dart'; +import 'core/theme/theme_data.dart'; +import 'core/theme/theme_provider.dart'; import 'l10n/app_localizations.dart'; import 'localization/language.dart'; import 'localization/language_app_provider.dart'; -import 'router/go_router.dart'; -import 'theme/theme_data.dart'; -import 'theme/theme_provider.dart'; +import 'routing/app_router.dart'; void main() { usePathUrlStrategy(); - runApp(const ProviderScope(child: MainApp())); } ///This widget is the root of your application. -class MainApp extends ConsumerStatefulWidget { +class MainApp extends ConsumerWidget { const MainApp({super.key}); - @override - ConsumerState createState() => _MainAppState(); -} - -class _MainAppState extends ConsumerState { - @override - void initState() { - super.initState(); + void _initAndroid() { + if (TargetPlatform.android != defaultTargetPlatform) return; SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( - systemNavigationBarColor: Colors.transparent, - ), + const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent), ); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } @override - Widget build(BuildContext context) { - final router = ref.watch(goRouterProvider); + Widget build(BuildContext context, WidgetRef ref) { + _initAndroid(); + final router = ref.watch(appRouterProvider); final themeMode = ref.watch(appThemeModeProvider); final isTrueBlack = ref.watch(appThemeTrueBlackProvider); final language = ref.watch(languageAppProvider).valueOrNull; - return MaterialApp.router( routerConfig: router, onGenerateTitle: (context) => context.loc.appTitle, diff --git a/lib/router/go_router.dart b/lib/routing/app_router.dart similarity index 90% rename from lib/router/go_router.dart rename to lib/routing/app_router.dart index a0a0a3e..5921e13 100644 --- a/lib/router/go_router.dart +++ b/lib/routing/app_router.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../common_widgets/cupertino_sheet.dart'; +import '../core/providers/initialization_provider.dart'; import '../features/home/home_page.dart'; import '../features/home/home_page_controller.dart'; import '../features/instruments/details/instrument_details_page.dart'; @@ -10,15 +11,15 @@ import '../features/instruments/instruments_tab_page.dart'; import '../features/parades/parades_tab_page.dart'; import '../features/schools/details/school_details_page.dart'; import '../features/schools/schools_tab_page.dart'; -import '../initialization_page.dart'; +import '../initialization.dart'; -part 'go_router.g.dart'; +part 'app_router.g.dart'; /// Required by StatefulShellRoute in GoRouter final _rootNavigatorKey = GlobalKey(debugLabel: 'root'); @riverpod -GoRouter goRouter(GoRouterRef ref) { +GoRouter appRouter(AppRouterRef ref) { final controllers = IMap( {for (final tab in HomeTab.values) tab.name: ScrollController()}, ); @@ -158,25 +159,3 @@ void _scrollTabToTheTop(ScrollController controller) { ); } } - -class SheetPage extends Page { - const SheetPage({ - required this.child, - }) : super(key: const ValueKey('SheetPage')); - - final Widget child; - - static const String routeName = 'Modal Sheet'; - - @override - Route createRoute(BuildContext context) { - return ModalBottomSheetRoute( - settings: this, - builder: (context) => child, - isScrollControlled: true, - ); - } - - @override - String get name => routeName; -} diff --git a/lib/core/shared_preferences_provider.g.dart b/lib/routing/app_router.g.dart similarity index 53% rename from lib/core/shared_preferences_provider.g.dart rename to lib/routing/app_router.g.dart index 4eec3e9..fa0121f 100644 --- a/lib/core/shared_preferences_provider.g.dart +++ b/lib/routing/app_router.g.dart @@ -1,25 +1,24 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'shared_preferences_provider.dart'; +part of 'app_router.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$sharedPreferencesHash() => r'25eceea0052302f519f44a896409ba30ede45562'; +String _$appRouterHash() => r'a6c0ef4349db1a9cfbdfc28c33aeb1449843c097'; -/// See also [sharedPreferences]. -@ProviderFor(sharedPreferences) -final sharedPreferencesProvider = FutureProvider.internal( - sharedPreferences, - name: r'sharedPreferencesProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$sharedPreferencesHash, +/// See also [appRouter]. +@ProviderFor(appRouter) +final appRouterProvider = AutoDisposeProvider.internal( + appRouter, + name: r'appRouterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appRouterHash, dependencies: null, allTransitiveDependencies: null, ); -typedef SharedPreferencesRef = FutureProviderRef; +typedef AppRouterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/routing/refresh_listenable.dart b/lib/routing/refresh_listenable.dart new file mode 100644 index 0000000..8c9c433 --- /dev/null +++ b/lib/routing/refresh_listenable.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +/// This class was imported from the migration guide for GoRouter 5.0 +class GoRouterRefreshStream extends ChangeNotifier { + GoRouterRefreshStream(Stream stream) { + notifyListeners(); + _subscription = stream.asBroadcastStream().listen( + (dynamic _) => notifyListeners(), + ); + } + + late final StreamSubscription _subscription; + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} diff --git a/lib/utils/app_error_handler.dart b/lib/utils/app_error_handler.dart new file mode 100644 index 0000000..10c52ad --- /dev/null +++ b/lib/utils/app_error_handler.dart @@ -0,0 +1,26 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +void registerErrorHandlers() { + // * Show some error UI if any uncaught exception happens + FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.presentError(details); + debugPrint(details.toString()); + }; + // * Handle errors from the underlying platform/OS + PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { + debugPrint(error.toString()); + return true; + }; + // * Show some error UI when any widget in the app fails to build + ErrorWidget.builder = (FlutterErrorDetails details) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.red, + title: const Text('An error occurred'), + ), + body: Center(child: Text(details.toString())), + ); + }; +} diff --git a/lib/utils/main_logger.dart b/lib/utils/app_loggers.dart similarity index 100% rename from lib/utils/main_logger.dart rename to lib/utils/app_loggers.dart diff --git a/lib/core/client_network_provider.g.dart b/lib/utils/client_network_provider.g.dart similarity index 94% rename from lib/core/client_network_provider.g.dart rename to lib/utils/client_network_provider.g.dart index 2be02a2..cb37db0 100644 --- a/lib/core/client_network_provider.g.dart +++ b/lib/utils/client_network_provider.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'client_network_provider.dart'; +part of '../core/providers/client_network_provider.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/pubspec.lock b/pubspec.lock index bd356ed..edc430b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -677,6 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 + url: "https://pub.dev" + source: hosted + version: "1.0.3" native_dio_adapter: dependency: "direct main" description: @@ -809,10 +817,10 @@ packages: dependency: "direct main" description: name: pull_down_button - sha256: "235b302701ce029fd9e9470975069376a6700935bb47a5f1b3ec8a5efba07e6f" + sha256: "48b928203afdeafa4a8be5dc96980523bc8a2ddbd04569f766071a722be22379" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.4" riverpod: dependency: transitive description: @@ -1134,10 +1142,10 @@ packages: dependency: transitive description: name: web_socket - sha256: "6bbfb36ea811dac44d18511648687d5334cbad736f45880520f34995861d9b11" + sha256: "7c57c6d0eb006e64d962d5c832bbe2ae9adbd887829944065f7467cbe13f6d8f" url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.1.2" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6b34d49..9e1b22d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,7 @@ dev_dependencies: flutter_test: sdk: flutter icons_launcher: ^2.1.7 + mocktail: ^1.0.3 riverpod_generator: ^3.0.0-dev.11 riverpod_lint: ^3.0.0-dev.4 very_good_analysis: ^5.1.0 diff --git a/test/core/providers/prefs_provider_test.dart b/test/core/providers/prefs_provider_test.dart new file mode 100644 index 0000000..1e21e3e --- /dev/null +++ b/test/core/providers/prefs_provider_test.dart @@ -0,0 +1,54 @@ +import 'package:batucadapp/core/providers/prefs_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + ProviderContainer makeProviderContainer(MockSharedPreferences prefs) { + final container = ProviderContainer( + overrides: [prefsProvider.overrideWith((_) => prefs)], + ); + addTearDown(container.dispose); + return container; + } + + test('Should return a SharedPreferences instance', () async { + final prefs = MockSharedPreferences(); + final container = makeProviderContainer(prefs); + await expectLater( + container.read(prefsProvider.future), + completion(prefs), + ); + }); +} + +class MockSharedPreferences extends Mock implements SharedPreferences { + //setMockInitialValues + static MockSharedPreferences setMockInitialValues( + Map values, + ) { + final mock = MockSharedPreferences(); + when(mock.getKeys).thenReturn(values.keys.toSet()); + for (final entry in values.entries) { + final key = entry.key; + final value = entry.value; + if (value is bool) { + when(() => mock.getBool(key)).thenReturn(value); + } else if (value is num) { + if (value is int) { + when(() => mock.getInt(key)).thenReturn(value); + } else if (value is double) { + when(() => mock.getDouble(key)).thenReturn(value); + } + } else if (value is String) { + when(() => mock.getString(key)).thenReturn(value); + } else if (value is List) { + when(() => mock.getStringList(key)).thenReturn(value); + } else { + throw UnimplementedError('Type ${value.runtimeType} not implemented'); + } + } + return mock; + } +} diff --git a/test/core/theme/theme_provider_test.dart b/test/core/theme/theme_provider_test.dart new file mode 100644 index 0000000..dee45f2 --- /dev/null +++ b/test/core/theme/theme_provider_test.dart @@ -0,0 +1,160 @@ +import 'package:batucadapp/core/providers/prefs_provider.dart'; +import 'package:batucadapp/core/theme/theme_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../create_container.dart'; +import '../providers/prefs_provider_test.dart'; + +ProviderContainer setupAppThemeModeProvider( + MockSharedPreferences? mockSharedPreferences, +) { + return createContainer( + overrides: [ + if (mockSharedPreferences != null) + prefsProvider.overrideWith((_) => mockSharedPreferences), + appThemeModeProvider.overrideWith(AppThemeMode.new), + ], + ); +} + +void main() { + group('appThemeTrueBlackProvider tests', () { + test('Return false when SharedPreferences doesnt have a value', () { + final container = setupAppThemeModeProvider(null); + final trueBlack = container.read(appThemeTrueBlackProvider); + expect(trueBlack, false); + }); + + test('Return true when SharedPreferences has a value', () { + final initialValues = {'true_black': true}; + final mock = MockSharedPreferences.setMockInitialValues(initialValues); + final container = setupAppThemeModeProvider(mock); + final trueBlack = container.read(appThemeTrueBlackProvider); + expect(trueBlack, true); + }); + + test('Toggle trueBlack to true', () { + final mock = MockSharedPreferences(); + when(() => mock.setBool('true_black', true)) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mock); + container.read(appThemeTrueBlackProvider.notifier).toggleTrueBlack(); + verify(() => mock.setBool('true_black', true)).called(1); + expect(container.read(appThemeTrueBlackProvider), true); + }); + + test('Toggle trueBlack to false', () { + final values = {'true_black': true}; + final mock = MockSharedPreferences.setMockInitialValues(values); + final container = setupAppThemeModeProvider(mock); + container.read(appThemeTrueBlackProvider.notifier).toggleTrueBlack(); + expect(container.read(appThemeTrueBlackProvider), false); + }); + }); + + group('Test the build of the appThemeProvider', () { + test(' ThemeMode.system when SharedPreferences has no value', () async { + final mockSharedPreferences = MockSharedPreferences(); + final container = setupAppThemeModeProvider(mockSharedPreferences); + final themeMode = container.read(appThemeModeProvider); + expect(themeMode, ThemeMode.system); + }); + + test(' ThemeMode.system when SharedPreferences is not init', () async { + final container = setupAppThemeModeProvider(null); + final themeMode = container.read(appThemeModeProvider); + expect(themeMode, ThemeMode.system); + }); + + test(' ThemeMode.light when SharedPreferences has value light', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.getString('theme_mode')) + .thenReturn('light'); + final container = setupAppThemeModeProvider(mockSharedPreferences); + final themeMode = container.read(appThemeModeProvider); + expect(themeMode, ThemeMode.light); + }); + + test(' ThemeMode.dark when SharedPreferences has value dark', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.getString('theme_mode')) + .thenReturn('dark'); + final container = setupAppThemeModeProvider(mockSharedPreferences); + final themeMode = container.read(appThemeModeProvider); + expect(themeMode, ThemeMode.dark); + }); + }); + + group('Test the setTheme function of appThemeProvider ', () { + test('Set ThemeMode.system', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.remove('theme_mode')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + expect(container.read(appThemeModeProvider), ThemeMode.system); + container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.system); + verify(() => mockSharedPreferences.remove('theme_mode')).called(1); + expect(container.read(appThemeModeProvider), ThemeMode.system); + }); + + test('Set ThemeMode.light', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.setString('theme_mode', 'light')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.light); + verify(() => mockSharedPreferences.setString('theme_mode', 'light')) + .called(1); + expect(container.read(appThemeModeProvider), ThemeMode.light); + }); + + test('Set ThemeMode.dark', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.setString('theme_mode', 'dark')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.dark); + verify(() => mockSharedPreferences.setString('theme_mode', 'dark')) + .called(1); + expect(container.read(appThemeModeProvider), ThemeMode.dark); + }); + }); + + group('Apptheme toggleTheme tests', () { + test('Toggle theme from ThemeMode.system', () async { + final mockSharedPreferences = MockSharedPreferences(); + when(() => mockSharedPreferences.setString('theme_mode', 'light')) + .thenAnswer((_) async => true); + when(() => mockSharedPreferences.remove('true_black')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + await container.read(appThemeModeProvider.notifier).toggleTheme(); + expect(container.read(appThemeModeProvider), ThemeMode.light); + }); + + test('Toggle theme from ThemeMode.light', () async { + final initialValue = {'theme_mode': 'light'}; + final mockSharedPreferences = + MockSharedPreferences.setMockInitialValues(initialValue); + when(() => mockSharedPreferences.setString('theme_mode', 'dark')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + await container.read(appThemeModeProvider.notifier).toggleTheme(); + expect(container.read(appThemeModeProvider), ThemeMode.dark); + }); + + test('Toggle theme from ThemeMode.dark', () async { + final initialValue = {'theme_mode': 'dark'}; + final mockSharedPreferences = + MockSharedPreferences.setMockInitialValues(initialValue); + when(() => mockSharedPreferences.remove('theme_mode')) + .thenAnswer((_) async => true); + final container = setupAppThemeModeProvider(mockSharedPreferences); + await container.read(appThemeModeProvider.notifier).toggleTheme(); + expect(container.read(appThemeModeProvider), ThemeMode.system); + }); + }); +} diff --git a/test/create_container.dart b/test/create_container.dart new file mode 100644 index 0000000..f85e5f1 --- /dev/null +++ b/test/create_container.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + addTearDown(container.dispose); + + return container; +} diff --git a/test/features/schools/schools_repo_test.dart b/test/features/schools/schools_repo_test.dart new file mode 100644 index 0000000..d802c27 --- /dev/null +++ b/test/features/schools/schools_repo_test.dart @@ -0,0 +1,13 @@ +// import 'package:batucadapp/features/schools/schools_repo.dart'; +// import 'package:flutter_test/flutter_test.dart'; + +// import '../../create_container.dart'; + +// import 'package:mocktail/mocktail.dart'; +// import 'package:riverpod/riverpod.dart'; + +// class MockNetworkClient extends Mock implements NetworkClient {} + +// class MockSchoolsRepoRef extends Mock implements SchoolsRepoRef {} + +// void main() {} diff --git a/test/localization/language_app_provider_test.dart b/test/localization/language_app_provider_test.dart new file mode 100644 index 0000000..a5992c6 --- /dev/null +++ b/test/localization/language_app_provider_test.dart @@ -0,0 +1,41 @@ +import 'package:batucadapp/core/providers/prefs_provider.dart'; +import 'package:batucadapp/localization/language.dart'; +import 'package:batucadapp/localization/language_app_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../core/providers/prefs_provider_test.dart'; +import '../create_container.dart'; + +ProviderContainer setupAppThemeModeProvider( + MockSharedPreferences? mockSharedPreferences, +) => + createContainer( + overrides: [ + if (mockSharedPreferences != null) + prefsProvider.overrideWith((_) => mockSharedPreferences), + languageAppProvider.overrideWith(LanguageApp.new), + ], + ); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + test('Return default when SharedPreferences doesnt have a value', () async { + final container = setupAppThemeModeProvider(MockSharedPreferences()); + final language = await container.read(languageAppProvider.future); + expect( + language?.languageCode, + TestWidgetsFlutterBinding.instance.platformDispatcher.locale.languageCode, + ); + }); + + test('Return value from SharedPreferences', () async { + final initialData = { + 'locale': Language.pt.languageCode, + }; + final mockPrefs = MockSharedPreferences.setMockInitialValues(initialData); + final container = setupAppThemeModeProvider(mockPrefs); + final language = await container.read(languageAppProvider.future); + expect(language, Language.pt); + }); +} From 4cdb27e6e368ab5c02dba4a24ceabb3bbe72d6a4 Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Fri, 19 Apr 2024 17:41:39 -0400 Subject: [PATCH 2/9] Add new tests, still problems in dio --- .github/workflows/test.yml | 9 + .github/workflows/web.yml | 3 + coverage/lcov.info | 316 +++++++++++------- lib/constants.dart | 8 + .../providers/client_network_provider.dart | 17 +- .../providers}/client_network_provider.g.dart | 4 +- lib/core/theme/theme_provider.g.dart | 28 +- .../adaptive_navigation_rail_footer.dart | 6 +- .../home/widgets/settings_modal_sheet.dart | 5 +- lib/localization/language.dart | 5 +- lib/localization/language_app_provider.dart | 12 +- lib/localization/language_app_provider.g.dart | 2 +- lib/utils/pagination_scroll_controller.dart | 46 --- .../client_network_provider_test.dart | 61 ++++ test/core/theme/theme_provider_test.dart | 56 ++-- test/create_container.dart | 18 - .../instruments/instruments_repo_test.dart | 1 + .../language_app_provider_test.dart | 90 +++-- 18 files changed, 402 insertions(+), 285 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 lib/constants.dart rename lib/{utils => core/providers}/client_network_provider.g.dart (87%) delete mode 100644 lib/utils/pagination_scroll_controller.dart create mode 100644 test/core/providers/client_network_provider_test.dart delete mode 100644 test/create_container.dart create mode 100644 test/features/instruments/instruments_repo_test.dart diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7a18dec --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,9 @@ +name: Run Tests +on: [push, workflow_dispatch] +jobs: + drive: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + - run: flutter test \ No newline at end of file diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index dd0fd4d..6b74560 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -20,6 +20,9 @@ jobs: channel: beta cache: true + - name: Run Tests + run: flutter test + - name: Install dependencies run: flutter pub get diff --git a/coverage/lcov.info b/coverage/lcov.info index 168c085..edb4101 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -10,129 +10,68 @@ DA:8,1 LF:2 LH:2 end_of_record -SF:lib/core/theme/theme_provider.dart -DA:11,1 -DA:14,5 -DA:16,3 -DA:21,1 -DA:22,4 -DA:23,2 -DA:24,1 -DA:25,2 -DA:27,1 -DA:34,1 -DA:37,5 -DA:39,1 -DA:40,1 -DA:44,3 -DA:49,1 -DA:50,1 -DA:51,1 -DA:52,4 -DA:53,1 -DA:54,1 -DA:55,3 -DA:56,1 -DA:57,1 -DA:58,1 -DA:59,4 -DA:60,1 -DA:61,1 -DA:62,1 -DA:63,4 -DA:64,1 -DA:65,1 -DA:69,1 -DA:70,4 -DA:72,1 -DA:73,1 -DA:74,1 -DA:75,1 -DA:76,1 -DA:77,1 -DA:78,1 -DA:79,1 -DA:80,1 -LF:42 -LH:42 -end_of_record -SF:lib/core/theme/theme_provider.g.dart -DA:9,0 -DA:13,3 -DA:23,1 -DA:27,2 -DA:28,1 -LF:5 -LH:4 +SF:lib/constants.dart +DA:2,0 +LF:1 +LH:0 end_of_record -SF:lib/utils/app_loggers.dart -DA:4,0 -DA:5,3 -DA:6,0 -DA:7,0 -DA:8,0 -DA:12,0 -DA:14,0 -DA:16,0 -DA:18,0 -DA:19,0 +SF:lib/core/providers/client_network_provider.dart DA:20,0 DA:22,0 DA:23,0 +DA:24,0 DA:25,0 -DA:28,0 -DA:33,0 -DA:34,0 -DA:38,0 -DA:39,0 -DA:40,0 -DA:41,0 -DA:42,0 -DA:44,0 -DA:49,0 -DA:50,0 -DA:51,0 -DA:52,0 -DA:53,0 -DA:57,0 -LF:29 -LH:1 -end_of_record -SF:lib/localization/language.dart -DA:12,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:23,0 DA:26,0 DA:27,0 -DA:28,0 DA:29,0 -DA:30,0 -DA:33,1 -DA:34,1 +DA:31,0 +DA:32,0 DA:35,0 -DA:36,0 DA:37,0 +DA:38,0 +DA:39,0 DA:40,0 DA:41,0 -DA:42,0 -DA:43,0 -DA:44,0 -DA:47,0 +DA:45,0 DA:49,0 DA:50,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 +DA:51,0 +DA:52,0 DA:59,0 -LF:33 +DA:61,0 +DA:62,0 +DA:64,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:87,0 +DA:89,0 +DA:90,0 +DA:94,0 +DA:95,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +LF:48 +LH:0 +end_of_record +SF:lib/core/providers/client_network_provider.g.dart +DA:9,0 +DA:13,2 +DA:14,1 +LF:3 LH:2 end_of_record SF:lib/localization/language_app_provider.dart @@ -140,21 +79,20 @@ DA:10,1 DA:12,4 DA:13,1 DA:14,4 -DA:15,1 -DA:16,3 -DA:17,0 -DA:21,0 -DA:25,0 -DA:27,0 -DA:29,0 -DA:31,0 -LF:12 -LH:6 +DA:15,4 +DA:18,1 +DA:21,4 +DA:22,1 +DA:23,1 +DA:25,2 +DA:27,2 +LF:11 +LH:11 end_of_record SF:lib/localization/language_app_provider.g.dart DA:9,0 -DA:13,2 -DA:14,1 +DA:13,4 +DA:14,2 LF:3 LH:2 end_of_record @@ -167,7 +105,7 @@ SF:lib/l10n/app_localizations.dart DA:65,0 DA:69,0 DA:70,0 -DA:636,1 +DA:636,3 DA:638,0 DA:640,0 DA:643,0 @@ -183,6 +121,87 @@ DA:661,0 LF:16 LH:1 end_of_record +SF:lib/core/providers/client_network/network_client_adapter.dart +DA:5,0 +DA:6,0 +DA:7,0 +DA:8,0 +DA:9,0 +DA:14,0 +DA:15,0 +LF:7 +LH:0 +end_of_record +SF:lib/localization/language.dart +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:47,1 +DA:48,2 +DA:49,4 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +LF:33 +LH:7 +end_of_record +SF:lib/utils/app_loggers.dart +DA:4,0 +DA:5,3 +DA:6,0 +DA:7,0 +DA:8,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +DA:28,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:57,0 +LF:29 +LH:1 +end_of_record SF:lib/l10n/app_localizations_en.dart DA:5,0 DA:7,0 @@ -559,3 +578,58 @@ DA:271,0 LF:90 LH:0 end_of_record +SF:lib/core/theme/theme_provider.dart +DA:11,1 +DA:14,5 +DA:16,3 +DA:21,1 +DA:22,4 +DA:23,2 +DA:24,1 +DA:25,2 +DA:27,1 +DA:34,1 +DA:37,5 +DA:39,1 +DA:40,1 +DA:44,3 +DA:49,1 +DA:50,1 +DA:51,1 +DA:52,4 +DA:53,1 +DA:54,1 +DA:55,3 +DA:56,1 +DA:57,1 +DA:58,1 +DA:59,4 +DA:60,1 +DA:61,1 +DA:62,1 +DA:63,4 +DA:64,1 +DA:65,1 +DA:69,1 +DA:70,4 +DA:72,1 +DA:73,1 +DA:74,1 +DA:75,1 +DA:76,1 +DA:77,1 +DA:78,1 +DA:79,1 +DA:80,1 +LF:42 +LH:42 +end_of_record +SF:lib/core/theme/theme_provider.g.dart +DA:9,1 +DA:13,2 +DA:14,1 +DA:25,0 +DA:29,3 +LF:5 +LH:4 +end_of_record diff --git a/lib/constants.dart b/lib/constants.dart new file mode 100644 index 0000000..64143da --- /dev/null +++ b/lib/constants.dart @@ -0,0 +1,8 @@ +final class AppConstants { + AppConstants._(); + + // Network + static const baseUrlPath = 'https://samba.deno.dev'; + static const connectTimeout = Duration(seconds: 2); + static const receiveTimeout = Duration(seconds: 3); +} diff --git a/lib/core/providers/client_network_provider.dart b/lib/core/providers/client_network_provider.dart index da645a5..990cacf 100644 --- a/lib/core/providers/client_network_provider.dart +++ b/lib/core/providers/client_network_provider.dart @@ -6,17 +6,14 @@ import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_stor import 'package:path_provider/path_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../constants.dart'; import '../../localization/language.dart'; import '../../localization/language_app_provider.dart'; import '../../utils/app_loggers.dart'; import 'client_network/network_client_adapter.dart' if (dart.library.js_interop) 'client_network/network_client_adapter_web.dart'; -part '../../utils/client_network_provider.g.dart'; - -const _baseUrlPath = 'https://samba.deno.dev'; -const _connectTimeout = Duration(seconds: 2); -const _receiveTimeout = Duration(seconds: 3); +part 'client_network_provider.g.dart'; @Riverpod(keepAlive: true) class ClientNetwork extends _$ClientNetwork { @@ -33,11 +30,9 @@ class ClientNetwork extends _$ClientNetwork { ); final options = BaseOptions( baseUrl: Endpoint.basePath.path, - connectTimeout: _connectTimeout, - receiveTimeout: _receiveTimeout, - queryParameters: { - 'language': language, - }, + connectTimeout: AppConstants.connectTimeout, + receiveTimeout: AppConstants.receiveTimeout, + queryParameters: {'language': language}, ); final dio = Dio(options) ..httpClientAdapter = getNativeAdapter(cronetHttp2: true) @@ -81,7 +76,7 @@ enum Endpoint { String get pathId => '$path/'; String get pathSearch => '$path/search'; String get path => switch (this) { - basePath => _baseUrlPath, + basePath => AppConstants.baseUrlPath, parades => '/parades', instruments => '/instruments', schools => '/schools', diff --git a/lib/utils/client_network_provider.g.dart b/lib/core/providers/client_network_provider.g.dart similarity index 87% rename from lib/utils/client_network_provider.g.dart rename to lib/core/providers/client_network_provider.g.dart index cb37db0..19b51a1 100644 --- a/lib/utils/client_network_provider.g.dart +++ b/lib/core/providers/client_network_provider.g.dart @@ -1,12 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of '../core/providers/client_network_provider.dart'; +part of 'client_network_provider.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$clientNetworkHash() => r'e3f371ef67d6880104ff979657ee9463af9d8a01'; +String _$clientNetworkHash() => r'5940f41520ab43d432e0010ff1324a03c92da85c'; /// See also [ClientNetwork]. @ProviderFor(ClientNetwork) diff --git a/lib/core/theme/theme_provider.g.dart b/lib/core/theme/theme_provider.g.dart index 2f98a20..0642824 100644 --- a/lib/core/theme/theme_provider.g.dart +++ b/lib/core/theme/theme_provider.g.dart @@ -6,20 +6,6 @@ part of 'theme_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$appThemeModeHash() => r'b0bcd77f3e7da8c63dfa315a5a07c720309d6bc9'; - -/// See also [AppThemeMode]. -@ProviderFor(AppThemeMode) -final appThemeModeProvider = NotifierProvider.internal( - AppThemeMode.new, - name: r'appThemeModeProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$appThemeModeHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$AppThemeMode = Notifier; String _$appThemeTrueBlackHash() => r'45e4f39626cbefaed196f5d2a35c786af909cab9'; /// See also [AppThemeTrueBlack]. @@ -36,5 +22,19 @@ final appThemeTrueBlackProvider = ); typedef _$AppThemeTrueBlack = AutoDisposeNotifier; +String _$appThemeModeHash() => r'f7bbd1bb2bb4b87989403e6b45e606a1f3efc952'; + +/// See also [AppThemeMode]. +@ProviderFor(AppThemeMode) +final appThemeModeProvider = NotifierProvider.internal( + AppThemeMode.new, + name: r'appThemeModeProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appThemeModeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AppThemeMode = Notifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart index 86c5dcf..a0f6986 100644 --- a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart +++ b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart @@ -60,11 +60,7 @@ class AdaptiveNavigationRailFooter extends ConsumerWidget { subtitle: language.nativeName, selected: language == ref.watch(languageAppProvider).value, onTap: () { - ref.read(languageAppProvider.notifier).setLanguage( - language, - isSameAsPlatform: - language.languageCode == locale.languageCode, - ); + ref.read(languageAppProvider.notifier).setLanguage(language); }, ), ], diff --git a/lib/features/home/widgets/settings_modal_sheet.dart b/lib/features/home/widgets/settings_modal_sheet.dart index 7cdd6b8..8e291e3 100644 --- a/lib/features/home/widgets/settings_modal_sheet.dart +++ b/lib/features/home/widgets/settings_modal_sheet.dart @@ -92,10 +92,7 @@ class SettingsLanguageSection extends ConsumerWidget { color: context.colorScheme.primary, ), onTap: () { - ref.read(languageAppProvider.notifier).setLanguage( - language, - isSameAsPlatform: language.isSameAsPlatform, - ); + ref.read(languageAppProvider.notifier).setLanguage(language); }, title: Text( language.nativeName, diff --git a/lib/localization/language.dart b/lib/localization/language.dart index b907c33..53d4868 100644 --- a/lib/localization/language.dart +++ b/lib/localization/language.dart @@ -45,9 +45,8 @@ extension LanguageExtension on Language { }; bool get isSameAsPlatform { - final platformLanguage = - WidgetsBinding.instance.platformDispatcher.locale.languageCode; - return languageCode == platformLanguage; + final platformLanguage = WidgetsBinding.instance.platformDispatcher; + return languageCode == platformLanguage.locale.languageCode; } } diff --git a/lib/localization/language_app_provider.dart b/lib/localization/language_app_provider.dart index 60e4c33..0822070 100644 --- a/lib/localization/language_app_provider.dart +++ b/lib/localization/language_app_provider.dart @@ -12,18 +12,14 @@ class LanguageApp extends _$LanguageApp { final prefs = await ref.watch(prefsProvider.future); final localeKey = prefs.getString('locale') ?? WidgetsBinding.instance.platformDispatcher.locale.languageCode; - return Language.values.firstWhere( - (e) => e.languageCode == localeKey, - orElse: () => Language.en, - ); + return Language.values.firstWhere((e) => e.languageCode == localeKey); } Future setLanguage( - Language language, { - required bool isSameAsPlatform, - }) async { + Language language, + ) async { final prefs = ref.read(prefsProvider).value; - if (isSameAsPlatform) { + if (language.isSameAsPlatform) { await prefs!.remove('locale'); } else { await prefs!.setString('locale', language.languageCode); diff --git a/lib/localization/language_app_provider.g.dart b/lib/localization/language_app_provider.g.dart index 4687bff..3d5b320 100644 --- a/lib/localization/language_app_provider.g.dart +++ b/lib/localization/language_app_provider.g.dart @@ -6,7 +6,7 @@ part of 'language_app_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$languageAppHash() => r'2949ce52a24ff04ecfdc4428033b35b6f27170c9'; +String _$languageAppHash() => r'e52112c6cd9856f2bf3ac95d9aa646ace4d03ed8'; /// See also [LanguageApp]. @ProviderFor(LanguageApp) diff --git a/lib/utils/pagination_scroll_controller.dart b/lib/utils/pagination_scroll_controller.dart deleted file mode 100644 index 176561d..0000000 --- a/lib/utils/pagination_scroll_controller.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; - -typedef AsyncBoolFunction = Future Function(); - -class PaginationScrollController { - late ScrollController scrollController; - bool isLoading = false; - bool stopLoading = false; - int currentPage = 1; - double boundaryOffset = 0.5; - late AsyncBoolFunction loadAction; - - void init({ - required AsyncBoolFunction onLoadMore, - ScrollController? controller, - }) { - loadAction = onLoadMore; - scrollController = controller ?? ScrollController() - ..addListener(scrollListener); - } - - void dispose() { - scrollController - ..removeListener(scrollListener) - ..dispose(); - } - - void scrollListener() { - if (!stopLoading) { - //load more data - if (scrollController.offset >= - scrollController.position.maxScrollExtent * boundaryOffset && - !isLoading) { - isLoading = true; - loadAction().then((bool shouldStop) { - isLoading = false; - currentPage++; - boundaryOffset = 1 - 1 / (currentPage * 2); - if (shouldStop == true) { - stopLoading = true; - } - }); - } - } - } -} diff --git a/test/core/providers/client_network_provider_test.dart b/test/core/providers/client_network_provider_test.dart new file mode 100644 index 0000000..757694b --- /dev/null +++ b/test/core/providers/client_network_provider_test.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:batucadapp/constants.dart'; +import 'package:batucadapp/core/providers/client_network_provider.dart'; +import 'package:batucadapp/localization/language_app_provider.dart'; +import 'package:dio/dio.dart'; +import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockDio extends Mock implements Dio {} + +class DioAdapterMock extends Mock implements HttpClientAdapter {} + +class MockClientNetwork extends AsyncNotifier + with Mock + implements ClientNetwork {} + +void main() { + ProviderContainer makeProviderContainer( + MockClientNetwork mockClientNetwork, + ) { + final container = ProviderContainer( + overrides: [ + languageAppProvider.overrideWith(LanguageApp.new), + clientNetworkProvider.overrideWith(() => mockClientNetwork), + ], + ); + addTearDown(container.dispose); + return container; + } + + group('ClientNetwork build', () { + test('Dio is configured with correct base URL and language', () async { + final dio = Dio(); + final mock = MockClientNetwork(); + when(mock.build).thenAnswer((_) async => dio); + final container = makeProviderContainer(mock); + + await expectLater( + container.read(clientNetworkProvider.future), + completion(isA()), + ); + }); + + test('Dio is configured with correct base URL and language', () async { + final dio = MockDio()..options.baseUrl = Endpoint.basePath.path; + final mock = MockClientNetwork(); + when(mock.build).thenAnswer((_) async => dio); + final container = makeProviderContainer(mock); + + final url = await container.read(clientNetworkProvider.future); + + await expectLater( + url, + AppConstants.baseUrlPath, + ); + });Te + }); +} diff --git a/test/core/theme/theme_provider_test.dart b/test/core/theme/theme_provider_test.dart index dee45f2..0ede6f7 100644 --- a/test/core/theme/theme_provider_test.dart +++ b/test/core/theme/theme_provider_test.dart @@ -5,25 +5,26 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import '../../create_container.dart'; import '../providers/prefs_provider_test.dart'; -ProviderContainer setupAppThemeModeProvider( - MockSharedPreferences? mockSharedPreferences, -) { - return createContainer( - overrides: [ - if (mockSharedPreferences != null) - prefsProvider.overrideWith((_) => mockSharedPreferences), - appThemeModeProvider.overrideWith(AppThemeMode.new), - ], - ); -} - void main() { + ProviderContainer makeProviderContainer( + MockSharedPreferences? mockSharedPreferences, + ) { + final container = ProviderContainer( + overrides: [ + if (mockSharedPreferences != null) + prefsProvider.overrideWith((_) => mockSharedPreferences), + appThemeModeProvider.overrideWith(AppThemeMode.new), + ], + ); + addTearDown(container.dispose); + return container; + } + group('appThemeTrueBlackProvider tests', () { test('Return false when SharedPreferences doesnt have a value', () { - final container = setupAppThemeModeProvider(null); + final container = makeProviderContainer(null); final trueBlack = container.read(appThemeTrueBlackProvider); expect(trueBlack, false); }); @@ -31,7 +32,7 @@ void main() { test('Return true when SharedPreferences has a value', () { final initialValues = {'true_black': true}; final mock = MockSharedPreferences.setMockInitialValues(initialValues); - final container = setupAppThemeModeProvider(mock); + final container = makeProviderContainer(mock); final trueBlack = container.read(appThemeTrueBlackProvider); expect(trueBlack, true); }); @@ -40,7 +41,7 @@ void main() { final mock = MockSharedPreferences(); when(() => mock.setBool('true_black', true)) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mock); + final container = makeProviderContainer(mock); container.read(appThemeTrueBlackProvider.notifier).toggleTrueBlack(); verify(() => mock.setBool('true_black', true)).called(1); expect(container.read(appThemeTrueBlackProvider), true); @@ -49,7 +50,8 @@ void main() { test('Toggle trueBlack to false', () { final values = {'true_black': true}; final mock = MockSharedPreferences.setMockInitialValues(values); - final container = setupAppThemeModeProvider(mock); + when(() => mock.remove('true_black')).thenAnswer((_) async => true); + final container = makeProviderContainer(mock); container.read(appThemeTrueBlackProvider.notifier).toggleTrueBlack(); expect(container.read(appThemeTrueBlackProvider), false); }); @@ -58,13 +60,13 @@ void main() { group('Test the build of the appThemeProvider', () { test(' ThemeMode.system when SharedPreferences has no value', () async { final mockSharedPreferences = MockSharedPreferences(); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); final themeMode = container.read(appThemeModeProvider); expect(themeMode, ThemeMode.system); }); test(' ThemeMode.system when SharedPreferences is not init', () async { - final container = setupAppThemeModeProvider(null); + final container = makeProviderContainer(null); final themeMode = container.read(appThemeModeProvider); expect(themeMode, ThemeMode.system); }); @@ -73,7 +75,7 @@ void main() { final mockSharedPreferences = MockSharedPreferences(); when(() => mockSharedPreferences.getString('theme_mode')) .thenReturn('light'); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); final themeMode = container.read(appThemeModeProvider); expect(themeMode, ThemeMode.light); }); @@ -82,7 +84,7 @@ void main() { final mockSharedPreferences = MockSharedPreferences(); when(() => mockSharedPreferences.getString('theme_mode')) .thenReturn('dark'); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); final themeMode = container.read(appThemeModeProvider); expect(themeMode, ThemeMode.dark); }); @@ -93,7 +95,7 @@ void main() { final mockSharedPreferences = MockSharedPreferences(); when(() => mockSharedPreferences.remove('theme_mode')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); expect(container.read(appThemeModeProvider), ThemeMode.system); container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.system); verify(() => mockSharedPreferences.remove('theme_mode')).called(1); @@ -104,7 +106,7 @@ void main() { final mockSharedPreferences = MockSharedPreferences(); when(() => mockSharedPreferences.setString('theme_mode', 'light')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.light); verify(() => mockSharedPreferences.setString('theme_mode', 'light')) .called(1); @@ -115,7 +117,7 @@ void main() { final mockSharedPreferences = MockSharedPreferences(); when(() => mockSharedPreferences.setString('theme_mode', 'dark')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.dark); verify(() => mockSharedPreferences.setString('theme_mode', 'dark')) .called(1); @@ -130,7 +132,7 @@ void main() { .thenAnswer((_) async => true); when(() => mockSharedPreferences.remove('true_black')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); await container.read(appThemeModeProvider.notifier).toggleTheme(); expect(container.read(appThemeModeProvider), ThemeMode.light); }); @@ -141,7 +143,7 @@ void main() { MockSharedPreferences.setMockInitialValues(initialValue); when(() => mockSharedPreferences.setString('theme_mode', 'dark')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); await container.read(appThemeModeProvider.notifier).toggleTheme(); expect(container.read(appThemeModeProvider), ThemeMode.dark); }); @@ -152,7 +154,7 @@ void main() { MockSharedPreferences.setMockInitialValues(initialValue); when(() => mockSharedPreferences.remove('theme_mode')) .thenAnswer((_) async => true); - final container = setupAppThemeModeProvider(mockSharedPreferences); + final container = makeProviderContainer(mockSharedPreferences); await container.read(appThemeModeProvider.notifier).toggleTheme(); expect(container.read(appThemeModeProvider), ThemeMode.system); }); diff --git a/test/create_container.dart b/test/create_container.dart deleted file mode 100644 index f85e5f1..0000000 --- a/test/create_container.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; - -ProviderContainer createContainer({ - ProviderContainer? parent, - List overrides = const [], - List? observers, -}) { - final container = ProviderContainer( - parent: parent, - overrides: overrides, - observers: observers, - ); - - addTearDown(container.dispose); - - return container; -} diff --git a/test/features/instruments/instruments_repo_test.dart b/test/features/instruments/instruments_repo_test.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/features/instruments/instruments_repo_test.dart @@ -0,0 +1 @@ + diff --git a/test/localization/language_app_provider_test.dart b/test/localization/language_app_provider_test.dart index a5992c6..0f07d66 100644 --- a/test/localization/language_app_provider_test.dart +++ b/test/localization/language_app_provider_test.dart @@ -1,41 +1,81 @@ import 'package:batucadapp/core/providers/prefs_provider.dart'; import 'package:batucadapp/localization/language.dart'; import 'package:batucadapp/localization/language_app_provider.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import '../core/providers/prefs_provider_test.dart'; -import '../create_container.dart'; -ProviderContainer setupAppThemeModeProvider( +ProviderContainer makeProviderContainer( MockSharedPreferences? mockSharedPreferences, -) => - createContainer( - overrides: [ - if (mockSharedPreferences != null) - prefsProvider.overrideWith((_) => mockSharedPreferences), - languageAppProvider.overrideWith(LanguageApp.new), - ], - ); +) { + final container = ProviderContainer( + overrides: [ + if (mockSharedPreferences != null) + prefsProvider.overrideWith((_) => mockSharedPreferences), + languageAppProvider.overrideWith(LanguageApp.new), + ], + ); + addTearDown(container.dispose); + return container; +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('Return default when SharedPreferences doesnt have a value', () async { - final container = setupAppThemeModeProvider(MockSharedPreferences()); - final language = await container.read(languageAppProvider.future); - expect( - language?.languageCode, - TestWidgetsFlutterBinding.instance.platformDispatcher.locale.languageCode, - ); + + group('Build Language App Provider', () { + test('Return default when SharedPreferences doesnt have a value', () async { + final container = makeProviderContainer(MockSharedPreferences()); + final language = await container.read(languageAppProvider.future); + expect( + language?.languageCode, + TestWidgetsFlutterBinding + .instance.platformDispatcher.locale.languageCode, + ); + }); + + test('Return value from SharedPreferences', () async { + final initialData = { + 'locale': Language.pt.languageCode, + }; + final mockPrefs = MockSharedPreferences.setMockInitialValues(initialData); + final container = makeProviderContainer(mockPrefs); + final language = await container.read(languageAppProvider.future); + expect(language, Language.pt); + }); }); - test('Return value from SharedPreferences', () async { - final initialData = { - 'locale': Language.pt.languageCode, - }; - final mockPrefs = MockSharedPreferences.setMockInitialValues(initialData); - final container = setupAppThemeModeProvider(mockPrefs); - final language = await container.read(languageAppProvider.future); - expect(language, Language.pt); + group('Set Language', () { + test('Set custom language to SharedPreferences', () async { + TestWidgetsFlutterBinding.instance.platformDispatcher.localeTestValue = + const Locale('en', ''); + final initialData = {'locale': Language.pt.languageCode}; + final mock = MockSharedPreferences.setMockInitialValues(initialData); + when(() => mock.setString('locale', any())).thenAnswer((_) async => true); + final container = makeProviderContainer(mock); + final languageApp = container.read(languageAppProvider.notifier); + await languageApp.setLanguage(Language.es); + await expectLater( + container.read(languageAppProvider.future), + completion(Language.es), + ); + }); + + test('Set default platform language', () async { + TestWidgetsFlutterBinding.instance.platformDispatcher.localeTestValue = + const Locale('es', ''); + final initialData = {'locale': Language.pt.languageCode}; + final mock = MockSharedPreferences.setMockInitialValues(initialData); + when(() => mock.remove('locale')).thenAnswer((_) async => true); + final container = makeProviderContainer(mock); + final languageApp = container.read(languageAppProvider.notifier); + await languageApp.setLanguage(Language.es); + await expectLater( + container.read(languageAppProvider.future), + completion(Language.es), + ); + }); }); } From 910b65bef8bf54fd49d5752cac2b80070b294fbf Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Sun, 21 Apr 2024 21:18:49 -0400 Subject: [PATCH 3/9] Update dependencies and add http_mock_adapter package --- analysis_options.yaml | 4 +- coverage/lcov.info | 746 ++++++++++++++++-- lib/core/app_provider.dart | 39 + lib/core/app_provider.g.dart | 38 + .../providers/client_network_provider.dart | 5 +- .../providers/client_network_provider.g.dart | 2 +- .../instruments/instruments_repo.dart | 6 +- .../instruments/instruments_repo.g.dart | 2 +- lib/localization/language_app_provider.dart | 7 +- lib/localization/language_app_provider.g.dart | 6 +- pubspec.lock | 24 + pubspec.yaml | 2 + test/analysis_options.yaml | 1 + test/core/app_provider_test.dart | 47 ++ .../client_network_provider_test.dart | 93 ++- .../language_app_provider_test.dart | 16 +- 16 files changed, 932 insertions(+), 106 deletions(-) create mode 100644 lib/core/app_provider.dart create mode 100644 lib/core/app_provider.g.dart create mode 100644 test/analysis_options.yaml create mode 100644 test/core/app_provider_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index b8e7339..7a69dad 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,7 +7,7 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. -include: package:very_good_analysis/analysis_options.yaml +include: package:solid_lints/analysis_options.yaml linter: # The lint rules applied to this project can be customized in the @@ -22,6 +22,7 @@ linter: # producing the lint. rules: public_member_api_docs: false + sort_constructors_first: true use_setters_to_change_properties: false one_member_abstracts: false always_use_package_imports: false @@ -31,4 +32,5 @@ analyzer: plugins: - custom_lint exclude: + - member_ordering: false - 'lib/l10n/*.dart' diff --git a/coverage/lcov.info b/coverage/lcov.info index edb4101..1be97b6 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,15 +1,3 @@ -SF:lib/core/providers/prefs_provider.g.dart -DA:9,1 -DA:13,9 -LF:2 -LH:2 -end_of_record -SF:lib/core/providers/prefs_provider.dart -DA:6,1 -DA:8,1 -LF:2 -LH:2 -end_of_record SF:lib/constants.dart DA:2,0 LF:1 @@ -23,37 +11,37 @@ DA:24,0 DA:25,0 DA:26,0 DA:27,0 -DA:29,0 -DA:31,0 +DA:28,0 +DA:30,0 DA:32,0 -DA:35,0 -DA:37,0 +DA:33,0 +DA:36,0 DA:38,0 DA:39,0 DA:40,0 DA:41,0 -DA:45,0 -DA:49,0 +DA:42,0 +DA:46,0 DA:50,0 DA:51,0 DA:52,0 -DA:59,0 -DA:61,0 +DA:53,0 +DA:60,0 DA:62,0 -DA:64,0 -DA:76,0 +DA:63,0 +DA:65,0 DA:77,0 DA:78,0 -DA:79,0 -DA:80,0 +DA:79,1 +DA:80,1 DA:81,0 DA:82,0 -DA:87,0 -DA:89,0 +DA:83,0 +DA:88,0 DA:90,0 -DA:94,0 +DA:91,0 DA:95,0 -DA:97,0 +DA:96,0 DA:98,0 DA:99,0 DA:100,0 @@ -64,8 +52,9 @@ DA:104,0 DA:105,0 DA:106,0 DA:107,0 -LF:48 -LH:0 +DA:108,0 +LF:49 +LH:2 end_of_record SF:lib/core/providers/client_network_provider.g.dart DA:9,0 @@ -74,28 +63,6 @@ DA:14,1 LF:3 LH:2 end_of_record -SF:lib/localization/language_app_provider.dart -DA:10,1 -DA:12,4 -DA:13,1 -DA:14,4 -DA:15,4 -DA:18,1 -DA:21,4 -DA:22,1 -DA:23,1 -DA:25,2 -DA:27,2 -LF:11 -LH:11 -end_of_record -SF:lib/localization/language_app_provider.g.dart -DA:9,0 -DA:13,4 -DA:14,2 -LF:3 -LH:2 -end_of_record SF:lib/core/extensions/app_localization_extension.dart DA:6,0 LF:1 @@ -105,7 +72,7 @@ SF:lib/l10n/app_localizations.dart DA:65,0 DA:69,0 DA:70,0 -DA:636,3 +DA:636,5 DA:638,0 DA:640,0 DA:643,0 @@ -152,7 +119,7 @@ DA:33,1 DA:34,1 DA:35,1 DA:36,1 -DA:37,0 +DA:37,1 DA:40,0 DA:41,0 DA:42,0 @@ -167,7 +134,31 @@ DA:56,0 DA:57,0 DA:58,0 LF:33 -LH:7 +LH:8 +end_of_record +SF:lib/localization/language_app_provider.dart +DA:10,1 +DA:12,4 +DA:13,1 +DA:14,4 +DA:15,1 +DA:16,3 +DA:17,1 +DA:21,1 +DA:24,4 +DA:25,1 +DA:26,1 +DA:28,2 +DA:30,2 +LF:13 +LH:13 +end_of_record +SF:lib/localization/language_app_provider.g.dart +DA:9,0 +DA:13,2 +DA:14,1 +LF:3 +LH:2 end_of_record SF:lib/utils/app_loggers.dart DA:4,0 @@ -202,6 +193,18 @@ DA:57,0 LF:29 LH:1 end_of_record +SF:lib/core/providers/prefs_provider.g.dart +DA:9,1 +DA:13,9 +LF:2 +LH:2 +end_of_record +SF:lib/core/providers/prefs_provider.dart +DA:6,1 +DA:8,1 +LF:2 +LH:2 +end_of_record SF:lib/l10n/app_localizations_en.dart DA:5,0 DA:7,0 @@ -633,3 +636,636 @@ DA:29,3 LF:5 LH:4 end_of_record +SF:lib/core/app_provider.dart +DA:9,0 +DA:17,1 +DA:19,1 +DA:20,4 +DA:21,2 +DA:27,3 +DA:30,0 +DA:34,0 +DA:37,0 +DA:38,0 +LF:10 +LH:5 +end_of_record +SF:lib/core/app_provider.g.dart +DA:9,0 +DA:13,3 +DA:23,0 +DA:27,3 +LF:4 +LH:2 +end_of_record +SF:lib/features/instruments/instrument.dart +DA:9,0 +LF:1 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_repo.dart +DA:10,0 +DA:12,0 +DA:22,0 +DA:26,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:51,0 +DA:53,0 +LF:20 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_repo.g.dart +DA:9,0 +DA:13,3 +LF:2 +LH:1 +end_of_record +SF:lib/utils/immutable_list.dart +DA:8,0 +DA:10,0 +DA:12,0 +DA:14,0 +DA:15,0 +LF:5 +LH:0 +end_of_record +SF:lib/common_widgets/app_back_button.dart +DA:7,1 +DA:9,0 +DA:11,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:20,0 +DA:21,0 +LF:8 +LH:1 +end_of_record +SF:lib/core/extensions/is_ios_or_macos_platform_extension.dart +DA:3,0 +DA:4,0 +DA:5,0 +LF:3 +LH:0 +end_of_record +SF:lib/core/extensions/theme_of_context_extension.dart +DA:7,0 +DA:9,0 +DA:10,0 +DA:11,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:31,0 +DA:32,0 +DA:33,0 +LF:18 +LH:0 +end_of_record +SF:lib/common_widgets/app_cupertino_button.dart +DA:17,0 +DA:30,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:68,0 +DA:69,0 +DA:70,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:86,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +LF:31 +LH:0 +end_of_record +SF:lib/common_widgets/app_cupertino_sliver_navigation_bar.dart +DA:13,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:45,0 +DA:46,0 +DA:50,0 +DA:51,0 +DA:55,0 +DA:56,0 +DA:57,0 +LF:20 +LH:0 +end_of_record +SF:lib/core/extensions/js_bottom_padding_extension.dart +DA:1,0 +DA:2,0 +DA:3,0 +DA:4,0 +LF:4 +LH:0 +end_of_record +SF:lib/features/home/widgets/settings_modal_sheet.dart +DA:12,0 +DA:17,0 +DA:23,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:68,1 +DA:70,0 +DA:72,0 +DA:74,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:80,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:92,0 +DA:94,0 +DA:95,0 +DA:97,0 +DA:98,0 +DA:101,0 +LF:35 +LH:1 +end_of_record +SF:lib/utils/screen_size.dart +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:25,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +LF:12 +LH:0 +end_of_record +SF:lib/common_widgets/app_fade_in_image.dart +DA:18,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:64,0 +DA:81,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:92,0 +DA:93,0 +DA:98,0 +DA:99,0 +DA:103,0 +DA:104,0 +LF:33 +LH:0 +end_of_record +SF:lib/common_widgets/app_web_padding.dart +DA:11,0 +DA:21,0 +DA:30,0 +DA:41,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:54,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:64,0 +DA:67,0 +DA:72,0 +DA:88,0 +DA:97,0 +DA:101,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:127,0 +LF:44 +LH:0 +end_of_record +SF:lib/core/theme/theme_data.dart +DA:7,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:36,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:64,0 +DA:67,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:96,0 +DA:104,0 +DA:111,0 +DA:118,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:132,0 +DA:134,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +LF:42 +LH:0 +end_of_record +SF:lib/features/home/widgets/settings_theme_section.dart +DA:9,1 +DA:11,0 +DA:13,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:47,0 +DA:49,0 +DA:54,0 +DA:55,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:68,0 +DA:69,0 +DA:71,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:77,0 +LF:39 +LH:1 +end_of_record +SF:lib/features/instruments/details/instrument_details_page.dart +DA:19,0 +DA:24,0 +DA:26,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:50,0 +DA:52,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:68,0 +DA:70,0 +DA:72,0 +DA:73,0 +DA:80,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:88,0 +DA:89,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:106,0 +DA:108,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:116,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:133,0 +DA:134,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:161,0 +DA:162,0 +LF:61 +LH:0 +end_of_record +SF:lib/features/instruments/details/instrument_details_providers.g.dart +DA:9,0 +DA:13,0 +DA:15,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:23,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:48,1 +DA:54,0 +DA:57,0 +DA:61,0 +DA:65,0 +DA:68,0 +DA:73,0 +DA:78,0 +DA:79,0 +DA:84,0 +DA:85,0 +DA:90,0 +DA:97,0 +DA:101,0 +DA:109,0 +DA:111,0 +DA:112,0 +DA:125,0 +DA:133,0 +DA:137,0 +DA:141,0 +DA:142,0 +DA:146,0 +DA:148,0 +DA:150,0 +DA:151,0 +DA:152,0 +DA:157,0 +DA:162,0 +DA:164,0 +DA:167,0 +DA:170,0 +DA:173,0 +DA:176,0 +DA:177,0 +DA:178,0 +DA:179,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:187,0 +DA:189,0 +DA:192,0 +DA:194,0 +DA:195,0 +DA:197,0 +DA:209,0 +DA:211,0 +DA:212,0 +LF:60 +LH:1 +end_of_record +SF:lib/features/instruments/details/instrument_details_providers.dart +DA:9,0 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:16,0 +LF:6 +LH:0 +end_of_record +SF:lib/features/instruments/details/widgets/instrument_details_summary.dart +DA:5,0 +DA:12,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:23,0 +LF:11 +LH:0 +end_of_record +SF:lib/features/instruments/details/widgets/instrument_header_images.dart +DA:12,0 +DA:22,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:60,0 +DA:61,0 +DA:73,0 +DA:74,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:92,0 +DA:96,0 +DA:98,0 +DA:99,0 +DA:110,0 +DA:111,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:140,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:153,0 +LF:55 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_tab_providers.dart +DA:11,0 +DA:13,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +LF:7 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_tab_providers.g.dart +DA:9,0 +DA:13,0 +LF:2 +LH:0 +end_of_record diff --git a/lib/core/app_provider.dart b/lib/core/app_provider.dart new file mode 100644 index 0000000..9e225fe --- /dev/null +++ b/lib/core/app_provider.dart @@ -0,0 +1,39 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'app_provider.g.dart'; + +@Riverpod(keepAlive: true) +class AppParent extends _$AppParent { + @override + FutureOr build() { + return const AppModel('AppParent'); + } +} + +@Riverpod(keepAlive: true) +class App extends _$App { + @override + FutureOr build() async { + await Future.delayed(const Duration(seconds: 1)); + final value = await ref.watch(appParentProvider.future); + return AppModel(value.name); + } +} + +@immutable +class AppModel { + const AppModel(this.name); + final String name; + + @override + bool operator ==(covariant AppModel other) { + if (identical(this, other)) return true; + + return other.name == name; + } + + @override + int get hashCode => name.hashCode; +} diff --git a/lib/core/app_provider.g.dart b/lib/core/app_provider.g.dart new file mode 100644 index 0000000..0da6b5b --- /dev/null +++ b/lib/core/app_provider.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$appParentHash() => r'8a8deb01447fb521a815b00cc1d0c9c801200c65'; + +/// See also [AppParent]. +@ProviderFor(AppParent) +final appParentProvider = AsyncNotifierProvider.internal( + AppParent.new, + name: r'appParentProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appParentHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AppParent = AsyncNotifier; +String _$appHash() => r'498ba010bd10d9d3de19f6aefde4544f4737cd8e'; + +/// See also [App]. +@ProviderFor(App) +final appProvider = AsyncNotifierProvider.internal( + App.new, + name: r'appProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$App = AsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/core/providers/client_network_provider.dart b/lib/core/providers/client_network_provider.dart index 990cacf..9818f00 100644 --- a/lib/core/providers/client_network_provider.dart +++ b/lib/core/providers/client_network_provider.dart @@ -18,9 +18,10 @@ part 'client_network_provider.g.dart'; @Riverpod(keepAlive: true) class ClientNetwork extends _$ClientNetwork { @override - Future build() async { - final language = ref.watch(languageAppProvider).value!.languageCode; + FutureOr build() async { final cacheDirPath = await _getTemporaryDirectory(); + final futureLanguage = await ref.watch(languageAppProvider.future); + final language = futureLanguage.languageCode; final cache = CacheOptions( store: BackupCacheStore( primary: MemCacheStore(), diff --git a/lib/core/providers/client_network_provider.g.dart b/lib/core/providers/client_network_provider.g.dart index 19b51a1..581cab0 100644 --- a/lib/core/providers/client_network_provider.g.dart +++ b/lib/core/providers/client_network_provider.g.dart @@ -6,7 +6,7 @@ part of 'client_network_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$clientNetworkHash() => r'5940f41520ab43d432e0010ff1324a03c92da85c'; +String _$clientNetworkHash() => r'5fb5d2868271a3c2c56090afd1ea4090c88252d3'; /// See also [ClientNetwork]. @ProviderFor(ClientNetwork) diff --git a/lib/features/instruments/instruments_repo.dart b/lib/features/instruments/instruments_repo.dart index 4a61391..2d61c42 100644 --- a/lib/features/instruments/instruments_repo.dart +++ b/lib/features/instruments/instruments_repo.dart @@ -9,7 +9,7 @@ part 'instruments_repo.g.dart'; @riverpod InstrumentsRepo instrumentsRepo(InstrumentsRepoRef ref) { - return InstrumentRepoImpls(ref); + return InstrumentsRepoImpls(ref); } abstract class InstrumentsRepo { @@ -18,8 +18,8 @@ abstract class InstrumentsRepo { Future getDetails(InstrumentId id); } -class InstrumentRepoImpls implements InstrumentsRepo { - InstrumentRepoImpls(this.ref); +class InstrumentsRepoImpls implements InstrumentsRepo { + InstrumentsRepoImpls(this.ref); final InstrumentsRepoRef ref; diff --git a/lib/features/instruments/instruments_repo.g.dart b/lib/features/instruments/instruments_repo.g.dart index d877104..79950bc 100644 --- a/lib/features/instruments/instruments_repo.g.dart +++ b/lib/features/instruments/instruments_repo.g.dart @@ -6,7 +6,7 @@ part of 'instruments_repo.dart'; // RiverpodGenerator // ************************************************************************** -String _$instrumentsRepoHash() => r'c18f42f8a1f93748803582ffb4297d1c76dfce20'; +String _$instrumentsRepoHash() => r'be669548808c3c86e523d75611dc91bdc21f2387'; /// See also [instrumentsRepo]. @ProviderFor(instrumentsRepo) diff --git a/lib/localization/language_app_provider.dart b/lib/localization/language_app_provider.dart index 0822070..49e9478 100644 --- a/lib/localization/language_app_provider.dart +++ b/lib/localization/language_app_provider.dart @@ -8,11 +8,14 @@ part 'language_app_provider.g.dart'; @Riverpod(keepAlive: true) class LanguageApp extends _$LanguageApp { @override - FutureOr build() async { + FutureOr build() async { final prefs = await ref.watch(prefsProvider.future); final localeKey = prefs.getString('locale') ?? WidgetsBinding.instance.platformDispatcher.locale.languageCode; - return Language.values.firstWhere((e) => e.languageCode == localeKey); + return Language.values.firstWhere( + (e) => e.languageCode == localeKey, + orElse: () => Language.en, + ); } Future setLanguage( diff --git a/lib/localization/language_app_provider.g.dart b/lib/localization/language_app_provider.g.dart index 3d5b320..f22a31b 100644 --- a/lib/localization/language_app_provider.g.dart +++ b/lib/localization/language_app_provider.g.dart @@ -6,12 +6,12 @@ part of 'language_app_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$languageAppHash() => r'e52112c6cd9856f2bf3ac95d9aa646ace4d03ed8'; +String _$languageAppHash() => r'ef2e6c2b06254f577831c8b2bdebcff29909220e'; /// See also [LanguageApp]. @ProviderFor(LanguageApp) final languageAppProvider = - AsyncNotifierProvider.internal( + AsyncNotifierProvider.internal( LanguageApp.new, name: r'languageAppProvider', debugGetCreateSourceHash: @@ -20,6 +20,6 @@ final languageAppProvider = allTransitiveDependencies: null, ); -typedef _$LanguageApp = AsyncNotifier; +typedef _$LanguageApp = AsyncNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/pubspec.lock b/pubspec.lock index edc430b..2f7e951 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -541,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + http_mock_adapter: + dependency: "direct dev" + description: + name: http_mock_adapter + sha256: "46399c78bd4a0af071978edd8c502d7aeeed73b5fb9860bca86b5ed647a63c1b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" http_multi_server: dependency: transitive description: @@ -637,6 +645,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + url: "https://pub.dev" + source: hosted + version: "2.2.0" logging: dependency: "direct main" description: @@ -962,6 +978,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.12" + solid_lints: + dependency: "direct dev" + description: + name: solid_lints + sha256: "3873959a106ca8683c9b64bfb331d318699fdae0e5c79a4420205f06f403f764" + url: "https://pub.dev" + source: hosted + version: "0.1.2" source_gen: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9e1b22d..2b519fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,10 +49,12 @@ dev_dependencies: flutter_native_splash: ^2.4.0 flutter_test: sdk: flutter + http_mock_adapter: ^0.6.1 icons_launcher: ^2.1.7 mocktail: ^1.0.3 riverpod_generator: ^3.0.0-dev.11 riverpod_lint: ^3.0.0-dev.4 + solid_lints: ^0.1.2 very_good_analysis: ^5.1.0 diff --git a/test/analysis_options.yaml b/test/analysis_options.yaml new file mode 100644 index 0000000..6e3f377 --- /dev/null +++ b/test/analysis_options.yaml @@ -0,0 +1 @@ +include: package:solid_lints/analysis_options_test.yaml \ No newline at end of file diff --git a/test/core/app_provider_test.dart b/test/core/app_provider_test.dart new file mode 100644 index 0000000..d313a54 --- /dev/null +++ b/test/core/app_provider_test.dart @@ -0,0 +1,47 @@ +// ignore_for_file: invalid_use_of_visible_for_overriding_member + +import 'package:batucadapp/core/app_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAppParent extends AsyncNotifier + with Mock + implements AppParent {} + +class MockApp extends AsyncNotifier with Mock implements App {} + +void main() { + test('Provider return the desired string', () async { + final mockAppParent = MockAppParent(); + + when(mockAppParent.build).thenReturn(const AppModel('AppParent')); + + final container = createContainer( + overrides: [ + appParentProvider.overrideWith(() => mockAppParent), + appProvider.overrideWith(App.new), + ], + ); + final provider = await container.read(appProvider.future); + expect(provider.name, 'AppParent'); + }); +} + +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // Create a ProviderContainer, and optionally allow specifying parameters. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // When the test ends, dispose the container. + addTearDown(container.dispose); + + return container; +} diff --git a/test/core/providers/client_network_provider_test.dart b/test/core/providers/client_network_provider_test.dart index 757694b..b72f069 100644 --- a/test/core/providers/client_network_provider_test.dart +++ b/test/core/providers/client_network_provider_test.dart @@ -1,61 +1,82 @@ -import 'dart:ui'; +// ignore_for_file: depend_on_referenced_packages, invalid_use_of_visible_for_overriding_member import 'package:batucadapp/constants.dart'; import 'package:batucadapp/core/providers/client_network_provider.dart'; -import 'package:batucadapp/localization/language_app_provider.dart'; import 'package:dio/dio.dart'; -import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:mocktail/mocktail.dart'; -class MockDio extends Mock implements Dio {} +class MockBaseOptions with Mock implements BaseOptions {} -class DioAdapterMock extends Mock implements HttpClientAdapter {} +class MockDio with Mock implements Dio {} + +class MockCacheInterceptor with Mock implements Interceptor {} + +class MockLogInterceptor with Mock implements Interceptor {} class MockClientNetwork extends AsyncNotifier with Mock implements ClientNetwork {} void main() { - ProviderContainer makeProviderContainer( - MockClientNetwork mockClientNetwork, - ) { - final container = ProviderContainer( - overrides: [ - languageAppProvider.overrideWith(LanguageApp.new), - clientNetworkProvider.overrideWith(() => mockClientNetwork), - ], - ); - addTearDown(container.dispose); - return container; - } - group('ClientNetwork build', () { test('Dio is configured with correct base URL and language', () async { - final dio = Dio(); + final baseOptions = BaseOptions( + baseUrl: Endpoint.basePath.path, + connectTimeout: AppConstants.connectTimeout, + receiveTimeout: AppConstants.receiveTimeout, + queryParameters: {'language': 'en'}, + ); + final dio = Dio(baseOptions); final mock = MockClientNetwork(); - when(mock.build).thenAnswer((_) async => dio); - final container = makeProviderContainer(mock); - - await expectLater( - container.read(clientNetworkProvider.future), - completion(isA()), + when(mock.build).thenReturn(dio); + final container = createContainer( + overrides: [clientNetworkProvider.overrideWith(() => mock)], ); - }); - test('Dio is configured with correct base URL and language', () async { - final dio = MockDio()..options.baseUrl = Endpoint.basePath.path; - final mock = MockClientNetwork(); - when(mock.build).thenAnswer((_) async => dio); - final container = makeProviderContainer(mock); + final readDio = await container.read(clientNetworkProvider.future); + DioAdapter(dio: readDio).onGet( + Endpoint.basePath.path, + (server) => server.reply(200, 'OK'), + ); - final url = await container.read(clientNetworkProvider.future); + final response = await readDio.get(Endpoint.basePath.path); - await expectLater( - url, - AppConstants.baseUrlPath, + expect(response.data, 'OK'); + expect( + readDio.options, + isA() + .having( + (options) => options.baseUrl, + 'baseUrl', + Endpoint.basePath.path, + ) + .having( + (options) => options.queryParameters, + 'queryParameters', + {'language': 'en'}, + ), ); - });Te + }); }); } + +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // Create a ProviderContainer, and optionally allow specifying parameters. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // When the test ends, dispose the container. + addTearDown(container.dispose); + + return container; +} diff --git a/test/localization/language_app_provider_test.dart b/test/localization/language_app_provider_test.dart index 0f07d66..9f59146 100644 --- a/test/localization/language_app_provider_test.dart +++ b/test/localization/language_app_provider_test.dart @@ -23,19 +23,31 @@ ProviderContainer makeProviderContainer( } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(TestWidgetsFlutterBinding.ensureInitialized); group('Build Language App Provider', () { test('Return default when SharedPreferences doesnt have a value', () async { final container = makeProviderContainer(MockSharedPreferences()); final language = await container.read(languageAppProvider.future); expect( - language?.languageCode, + language.languageCode, TestWidgetsFlutterBinding .instance.platformDispatcher.locale.languageCode, ); }); + test('Return default when SharedPreferences nor Flutter have a right value', + () async { + TestWidgetsFlutterBinding.instance.platformDispatcher.localeTestValue = + const Locale('und', ''); + final container = makeProviderContainer(MockSharedPreferences()); + final language = await container.read(languageAppProvider.future); + expect( + language.languageCode, + 'en', + ); + }); + test('Return value from SharedPreferences', () async { final initialData = { 'locale': Language.pt.languageCode, From 74ce80ba40614a1b68c24f32b9708b8df4404a5d Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Mon, 22 Apr 2024 01:06:15 -0400 Subject: [PATCH 4/9] Implement drop-in solid_lints add some tests, refactor rail(in progress) --- analysis_options.yaml | 24 +- coverage/lcov.info | 819 ++---------------- .../app_animated_linear_gradient.dart | 30 +- lib/common_widgets/app_animation_wrapper.dart | 46 +- lib/common_widgets/app_async_widget.dart | 3 +- lib/common_widgets/app_cupertino_button.dart | 38 +- .../app_cupertino_sliver_navigation_bar.dart | 3 +- lib/common_widgets/app_fade_in_image.dart | 1 + lib/common_widgets/app_image.dart | 21 - .../app_infinite_rotation_animation.dart | 22 +- lib/common_widgets/app_loading_indicator.dart | 1 + lib/common_widgets/app_web_padding.dart | 68 +- ..._sheet.dart => cupertino_sheet_route.dart} | 55 +- lib/constants.dart | 6 +- lib/core/app_provider.dart | 39 - lib/core/app_provider.g.dart | 38 - .../app_localization_extension.dart | 2 +- lib/core/extensions/context_snackbar.dart | 44 +- lib/core/extensions/hardcoded_extension.dart | 2 +- lib/core/extensions/intl_extension.dart | 2 + lib/core/extensions/router_extension.dart | 3 +- ..._extension.dart => string_extensions.dart} | 2 +- lib/core/extensions/text_lines_extension.dart | 28 + .../theme_of_context_extension.dart | 36 +- ...work_provider.dart => client_network.dart} | 22 +- ..._provider.g.dart => client_network.g.dart} | 4 +- .../network_client_adapter.dart | 3 +- .../network_client_adapter_web.dart | 3 +- ...tion_provider.dart => initialization.dart} | 4 +- ..._provider.g.dart => initialization.g.dart} | 2 +- .../{prefs_provider.dart => prefs.dart} | 7 +- .../{prefs_provider.g.dart => prefs.g.dart} | 4 +- .../theme/{theme_data.dart => app_theme.dart} | 5 +- ...ovider.dart => theme_mode_controller.dart} | 59 +- ...er.g.dart => theme_mode_controller.g.dart} | 35 +- lib/features/home/home_page.dart | 1 + lib/features/home/home_page_controller.dart | 2 +- lib/features/home/home_page_controller.g.dart | 22 +- .../home/widgets/adaptive_navigation_bar.dart | 4 +- .../widgets/adaptive_navigation_rail.dart | 1 + .../adaptive_navigation_rail_footer.dart | 241 +++--- .../home/widgets/settings_modal_sheet.dart | 30 +- .../home/widgets/settings_theme_section.dart | 8 +- ...art => instrument_details_controller.dart} | 9 +- ...t => instrument_details_controller.g.dart} | 106 +-- .../details/instrument_details_page.dart | 43 +- .../widgets/instrument_details_summary.dart | 1 + .../widgets/instrument_header_images.dart | 1 + lib/features/instruments/instrument.dart | 22 +- .../instruments/instruments_repo.dart | 32 +- ...s.dart => instruments_tab_controller.dart} | 4 +- .../instruments_tab_controller.g.dart | 28 + .../instruments/instruments_tab_page.dart | 8 +- .../widgets/instrument_list_tile.dart | 28 +- lib/features/parades/parade.dart | 80 +- lib/features/parades/parade_extension.dart | 49 +- lib/features/parades/parades_repo.dart | 31 +- ...iders.dart => parades_tab_controller.dart} | 14 +- ...s.g.dart => parades_tab_controller.g.dart} | 25 +- lib/features/parades/parades_tab_page.dart | 41 +- lib/features/parades/widgets/parade_item.dart | 21 +- .../widgets/parade_item_bottom_row.dart | 3 +- .../parades/widgets/parade_item_sidebar.dart | 3 +- .../schools/details/school_details_page.dart | 22 +- .../details/schools_details_controller.dart | 15 + ...dart => schools_details_controller.g.dart} | 105 +-- .../details/schools_details_providers.dart | 16 - lib/features/schools/school.dart | 85 +- lib/features/schools/school.mapper.dart | 38 +- lib/features/schools/school_color_hook.dart | 6 +- lib/features/schools/school_extensions.dart | 44 +- lib/features/schools/schools_repo.dart | 14 +- ...iders.dart => schools_tab_controller.dart} | 49 +- ...s.g.dart => schools_tab_controller.g.dart} | 31 +- lib/features/schools/schools_tab_page.dart | 31 +- lib/features/schools/widgets/school_card.dart | 44 +- .../schools/widgets/school_filter_chips.dart | 3 +- lib/features/schools/widgets/school_flag.dart | 5 +- .../schools/widgets/schools_empty_list.dart | 12 +- .../schools/widgets/schools_tab_body.dart | 18 +- .../schools/widgets/schools_tab_navbar.dart | 5 +- .../widgets/schools_tab_search_header.dart | 23 +- lib/initialization.dart | 12 +- lib/localization/language.dart | 1 + ...ider.dart => language_app_controller.dart} | 13 +- .../language_app_controller.g.dart} | 21 +- lib/localization/language_app_provider.g.dart | 25 - lib/main.dart | 15 +- lib/routing/app_router.dart | 279 +++--- lib/routing/app_router.g.dart | 13 +- lib/routing/refresh_listenable.dart | 9 +- lib/utils/app_error_handler.dart | 1 + lib/utils/app_loggers.dart | 3 +- lib/utils/debouncer.dart | 2 +- lib/utils/screen_size.dart | 14 + test/core/app_provider_test.dart | 47 - .../client_network_provider_test.dart | 12 +- test/core/providers/prefs_provider_test.dart | 4 +- test/core/theme/theme_provider_test.dart | 47 +- .../language_app_provider_test.dart | 26 +- 100 files changed, 1424 insertions(+), 2025 deletions(-) delete mode 100644 lib/common_widgets/app_image.dart rename lib/common_widgets/{cupertino_sheet.dart => cupertino_sheet_route.dart} (98%) delete mode 100644 lib/core/app_provider.dart delete mode 100644 lib/core/app_provider.g.dart rename lib/core/extensions/{string_extension.dart => string_extensions.dart} (93%) create mode 100644 lib/core/extensions/text_lines_extension.dart rename lib/core/providers/{client_network_provider.dart => client_network.dart} (88%) rename lib/core/providers/{client_network_provider.g.dart => client_network.g.dart} (89%) rename lib/core/providers/{initialization_provider.dart => initialization.dart} (89%) rename lib/core/providers/{initialization_provider.g.dart => initialization.g.dart} (96%) rename lib/core/providers/{prefs_provider.dart => prefs.dart} (56%) rename lib/core/providers/{prefs_provider.g.dart => prefs.g.dart} (90%) rename lib/core/theme/{theme_data.dart => app_theme.dart} (98%) rename lib/core/theme/{theme_provider.dart => theme_mode_controller.dart} (85%) rename lib/core/theme/{theme_provider.g.dart => theme_mode_controller.g.dart} (63%) rename lib/features/instruments/details/{instrument_details_providers.dart => instrument_details_controller.dart} (62%) rename lib/features/instruments/details/{instrument_details_providers.g.dart => instrument_details_controller.g.dart} (53%) rename lib/features/instruments/{instruments_tab_providers.dart => instruments_tab_controller.dart} (82%) create mode 100644 lib/features/instruments/instruments_tab_controller.g.dart rename lib/features/parades/{parades_tab_providers.dart => parades_tab_controller.dart} (85%) rename lib/features/parades/{parades_tab_providers.g.dart => parades_tab_controller.g.dart} (74%) create mode 100644 lib/features/schools/details/schools_details_controller.dart rename lib/features/schools/details/{schools_details_providers.g.dart => schools_details_controller.g.dart} (53%) delete mode 100644 lib/features/schools/details/schools_details_providers.dart rename lib/features/schools/{schools_tab_providers.dart => schools_tab_controller.dart} (81%) rename lib/features/schools/{schools_tab_providers.g.dart => schools_tab_controller.g.dart} (81%) rename lib/localization/{language_app_provider.dart => language_app_controller.dart} (69%) rename lib/{features/instruments/instruments_tab_providers.g.dart => localization/language_app_controller.g.dart} (59%) delete mode 100644 lib/localization/language_app_provider.g.dart delete mode 100644 test/core/app_provider_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 7a69dad..c4d544c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,12 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. +# include: package:very_good_analysis/analysis_options.yaml include: package:solid_lints/analysis_options.yaml + linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: public_member_api_docs: false - sort_constructors_first: true + sort_constructors_first: false use_setters_to_change_properties: false one_member_abstracts: false always_use_package_imports: false @@ -33,4 +17,4 @@ analyzer: - custom_lint exclude: - member_ordering: false - - 'lib/l10n/*.dart' + - 'lib/l10n/*.dart' \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info index 1be97b6..3f044f4 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,62 +1,62 @@ SF:lib/constants.dart -DA:2,0 +DA:7,0 LF:1 LH:0 end_of_record -SF:lib/core/providers/client_network_provider.dart +SF:lib/core/providers/client_network.dart DA:20,0 DA:22,0 -DA:23,0 DA:24,0 DA:25,0 DA:26,0 DA:27,0 DA:28,0 -DA:30,0 -DA:32,0 +DA:29,0 +DA:31,0 DA:33,0 -DA:36,0 -DA:38,0 +DA:34,0 +DA:37,0 DA:39,0 DA:40,0 DA:41,0 DA:42,0 -DA:46,0 -DA:50,0 +DA:43,0 +DA:47,0 DA:51,0 DA:52,0 DA:53,0 -DA:60,0 -DA:62,0 +DA:54,0 +DA:61,0 DA:63,0 DA:65,0 -DA:77,0 -DA:78,0 -DA:79,1 -DA:80,1 +DA:67,0 +DA:80,0 DA:81,0 -DA:82,0 -DA:83,0 -DA:88,0 -DA:90,0 -DA:91,0 +DA:82,1 +DA:83,1 +DA:84,0 +DA:85,0 +DA:86,0 +DA:93,0 DA:95,0 DA:96,0 DA:98,0 DA:99,0 -DA:100,0 DA:101,0 DA:102,0 -DA:103,0 DA:104,0 DA:105,0 DA:106,0 DA:107,0 DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 LF:49 LH:2 end_of_record -SF:lib/core/providers/client_network_provider.g.dart +SF:lib/core/providers/client_network.g.dart DA:9,0 DA:13,2 DA:14,1 @@ -72,7 +72,7 @@ SF:lib/l10n/app_localizations.dart DA:65,0 DA:69,0 DA:70,0 -DA:636,5 +DA:636,4 DA:638,0 DA:640,0 DA:643,0 @@ -88,17 +88,6 @@ DA:661,0 LF:16 LH:1 end_of_record -SF:lib/core/providers/client_network/network_client_adapter.dart -DA:5,0 -DA:6,0 -DA:7,0 -DA:8,0 -DA:9,0 -DA:14,0 -DA:15,0 -LF:7 -LH:0 -end_of_record SF:lib/localization/language.dart DA:12,0 DA:13,0 @@ -127,36 +116,36 @@ DA:43,0 DA:44,0 DA:47,1 DA:48,2 -DA:49,4 -DA:54,0 +DA:50,4 DA:55,0 DA:56,0 DA:57,0 DA:58,0 +DA:59,0 LF:33 LH:8 end_of_record -SF:lib/localization/language_app_provider.dart +SF:lib/localization/language_app_controller.dart DA:10,1 DA:12,4 DA:13,1 DA:14,4 -DA:15,1 -DA:16,3 -DA:17,1 -DA:21,1 -DA:24,4 -DA:25,1 +DA:16,1 +DA:17,3 +DA:18,1 +DA:22,1 +DA:25,4 DA:26,1 -DA:28,2 -DA:30,2 +DA:27,1 +DA:29,2 +DA:31,2 LF:13 LH:13 end_of_record -SF:lib/localization/language_app_provider.g.dart +SF:lib/localization/language_app_controller.g.dart DA:9,0 -DA:13,2 -DA:14,1 +DA:14,2 +DA:15,1 LF:3 LH:2 end_of_record @@ -185,21 +174,32 @@ DA:41,0 DA:42,0 DA:44,0 DA:49,0 -DA:50,0 DA:51,0 DA:52,0 DA:53,0 -DA:57,0 +DA:54,0 +DA:58,0 LF:29 LH:1 end_of_record -SF:lib/core/providers/prefs_provider.g.dart +SF:lib/core/providers/client_network/network_client_adapter.dart +DA:5,0 +DA:6,0 +DA:7,0 +DA:8,0 +DA:9,0 +DA:15,0 +DA:16,0 +LF:7 +LH:0 +end_of_record +SF:lib/core/providers/prefs.g.dart DA:9,1 DA:13,9 LF:2 LH:2 end_of_record -SF:lib/core/providers/prefs_provider.dart +SF:lib/core/providers/prefs.dart DA:6,1 DA:8,1 LF:2 @@ -581,691 +581,60 @@ DA:271,0 LF:90 LH:0 end_of_record -SF:lib/core/theme/theme_provider.dart +SF:lib/core/theme/theme_mode_controller.dart DA:11,1 -DA:14,5 -DA:16,3 -DA:21,1 -DA:22,4 -DA:23,2 -DA:24,1 -DA:25,2 -DA:27,1 +DA:14,4 +DA:15,1 +DA:18,1 +DA:19,1 +DA:23,0 +DA:29,1 +DA:30,1 +DA:31,1 +DA:32,4 +DA:33,1 DA:34,1 -DA:37,5 -DA:39,1 +DA:35,3 +DA:36,1 +DA:37,1 +DA:38,1 +DA:39,4 DA:40,1 -DA:44,3 +DA:41,1 +DA:42,1 +DA:43,4 +DA:44,1 +DA:45,1 DA:49,1 -DA:50,1 -DA:51,1 -DA:52,4 +DA:50,4 +DA:52,1 DA:53,1 DA:54,1 -DA:55,3 +DA:55,1 DA:56,1 DA:57,1 DA:58,1 -DA:59,4 +DA:59,1 DA:60,1 -DA:61,1 -DA:62,1 -DA:63,4 -DA:64,1 -DA:65,1 -DA:69,1 -DA:70,4 -DA:72,1 -DA:73,1 -DA:74,1 -DA:75,1 -DA:76,1 -DA:77,1 -DA:78,1 +DA:67,1 +DA:70,5 +DA:73,3 DA:79,1 -DA:80,1 -LF:42 +DA:80,4 +DA:81,2 +DA:82,1 +DA:83,2 +DA:85,1 +LF:43 LH:42 end_of_record -SF:lib/core/theme/theme_provider.g.dart -DA:9,1 -DA:13,2 -DA:14,1 -DA:25,0 -DA:29,3 -LF:5 -LH:4 -end_of_record -SF:lib/core/app_provider.dart -DA:9,0 -DA:17,1 -DA:19,1 -DA:20,4 -DA:21,2 -DA:27,3 -DA:30,0 -DA:34,0 -DA:37,0 -DA:38,0 -LF:10 -LH:5 -end_of_record -SF:lib/core/app_provider.g.dart -DA:9,0 -DA:13,3 -DA:23,0 -DA:27,3 -LF:4 -LH:2 -end_of_record -SF:lib/features/instruments/instrument.dart -DA:9,0 -LF:1 -LH:0 -end_of_record -SF:lib/features/instruments/instruments_repo.dart -DA:10,0 -DA:12,0 -DA:22,0 -DA:26,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:32,0 -DA:33,0 -DA:34,0 -DA:35,0 -DA:38,0 -DA:42,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:49,0 -DA:51,0 -DA:53,0 -LF:20 -LH:0 -end_of_record -SF:lib/features/instruments/instruments_repo.g.dart -DA:9,0 -DA:13,3 -LF:2 -LH:1 -end_of_record -SF:lib/utils/immutable_list.dart -DA:8,0 -DA:10,0 -DA:12,0 -DA:14,0 -DA:15,0 -LF:5 -LH:0 -end_of_record -SF:lib/common_widgets/app_back_button.dart -DA:7,1 -DA:9,0 -DA:11,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:20,0 -DA:21,0 -LF:8 -LH:1 -end_of_record -SF:lib/core/extensions/is_ios_or_macos_platform_extension.dart -DA:3,0 -DA:4,0 -DA:5,0 -LF:3 -LH:0 -end_of_record -SF:lib/core/extensions/theme_of_context_extension.dart -DA:7,0 -DA:9,0 -DA:10,0 -DA:11,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:25,0 -DA:26,0 -DA:27,0 -DA:28,0 -DA:31,0 -DA:32,0 -DA:33,0 -LF:18 -LH:0 -end_of_record -SF:lib/common_widgets/app_cupertino_button.dart -DA:17,0 -DA:30,0 -DA:53,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:68,0 -DA:69,0 -DA:70,0 -DA:72,0 -DA:73,0 -DA:74,0 -DA:75,0 -DA:76,0 -DA:77,0 -DA:78,0 -DA:80,0 -DA:81,0 -DA:82,0 -DA:84,0 -DA:86,0 -DA:95,0 -DA:96,0 -DA:97,0 -DA:98,0 -DA:99,0 -DA:100,0 -LF:31 -LH:0 -end_of_record -SF:lib/common_widgets/app_cupertino_sliver_navigation_bar.dart -DA:13,0 -DA:26,0 -DA:28,0 -DA:29,0 -DA:31,0 -DA:32,0 -DA:35,0 -DA:36,0 -DA:37,0 -DA:38,0 -DA:39,0 -DA:40,0 -DA:41,0 -DA:45,0 -DA:46,0 -DA:50,0 -DA:51,0 -DA:55,0 -DA:56,0 -DA:57,0 -LF:20 -LH:0 -end_of_record -SF:lib/core/extensions/js_bottom_padding_extension.dart -DA:1,0 -DA:2,0 -DA:3,0 -DA:4,0 -LF:4 -LH:0 -end_of_record -SF:lib/features/home/widgets/settings_modal_sheet.dart -DA:12,0 -DA:17,0 -DA:23,0 -DA:25,0 -DA:27,0 -DA:29,0 -DA:30,0 -DA:33,0 -DA:34,0 -DA:38,0 -DA:39,0 -DA:41,0 -DA:43,0 -DA:44,0 -DA:45,0 -DA:68,1 -DA:70,0 -DA:72,0 -DA:74,0 -DA:76,0 -DA:77,0 -DA:78,0 -DA:80,0 -DA:84,0 -DA:85,0 -DA:86,0 -DA:87,0 -DA:88,0 -DA:89,0 -DA:92,0 -DA:94,0 -DA:95,0 -DA:97,0 -DA:98,0 -DA:101,0 -LF:35 -LH:1 -end_of_record -SF:lib/utils/screen_size.dart -DA:15,0 -DA:16,0 -DA:17,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:25,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:32,0 -LF:12 -LH:0 -end_of_record -SF:lib/common_widgets/app_fade_in_image.dart -DA:18,0 -DA:37,0 -DA:39,0 -DA:40,0 -DA:41,0 -DA:43,0 -DA:44,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:49,0 -DA:50,0 -DA:51,0 -DA:52,0 -DA:53,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:64,0 -DA:81,0 -DA:83,0 -DA:84,0 -DA:85,0 -DA:87,0 -DA:88,0 -DA:89,0 -DA:92,0 -DA:93,0 -DA:98,0 -DA:99,0 -DA:103,0 -DA:104,0 -LF:33 -LH:0 -end_of_record -SF:lib/common_widgets/app_web_padding.dart -DA:11,0 -DA:21,0 -DA:30,0 -DA:41,0 -DA:44,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:49,0 -DA:50,0 -DA:51,0 -DA:52,0 -DA:54,0 -DA:57,0 -DA:58,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:62,0 -DA:64,0 -DA:67,0 -DA:72,0 -DA:88,0 -DA:97,0 -DA:101,0 -DA:104,0 -DA:105,0 -DA:106,0 -DA:107,0 -DA:108,0 -DA:109,0 -DA:110,0 -DA:111,0 -DA:112,0 -DA:114,0 -DA:117,0 -DA:118,0 -DA:119,0 -DA:120,0 -DA:121,0 -DA:122,0 -DA:124,0 -DA:127,0 -LF:44 -LH:0 -end_of_record -SF:lib/core/theme/theme_data.dart -DA:7,0 -DA:29,0 -DA:30,0 -DA:32,0 -DA:33,0 -DA:36,0 -DA:55,0 -DA:56,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:64,0 -DA:67,0 -DA:70,0 -DA:71,0 -DA:72,0 -DA:73,0 -DA:74,0 -DA:75,0 -DA:76,0 -DA:78,0 -DA:79,0 -DA:81,0 -DA:82,0 -DA:96,0 -DA:104,0 -DA:111,0 -DA:118,0 -DA:123,0 -DA:124,0 -DA:125,0 -DA:126,0 -DA:127,0 -DA:128,0 -DA:132,0 -DA:134,0 -DA:137,0 -DA:138,0 -DA:139,0 -DA:140,0 -DA:141,0 -DA:142,0 -LF:42 -LH:0 -end_of_record -SF:lib/features/home/widgets/settings_theme_section.dart -DA:9,1 -DA:11,0 -DA:13,0 -DA:14,0 -DA:16,0 -DA:17,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:23,0 -DA:27,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:33,0 -DA:35,0 -DA:36,0 -DA:38,0 -DA:39,0 -DA:41,0 -DA:43,0 -DA:45,0 -DA:47,0 -DA:49,0 -DA:54,0 -DA:55,0 -DA:57,0 -DA:58,0 -DA:61,0 -DA:63,0 -DA:64,0 -DA:65,0 -DA:68,0 -DA:69,0 -DA:71,0 -DA:73,0 -DA:75,0 -DA:76,0 -DA:77,0 -LF:39 -LH:1 -end_of_record -SF:lib/features/instruments/details/instrument_details_page.dart -DA:19,0 -DA:24,0 -DA:26,0 -DA:32,0 -DA:34,0 -DA:35,0 -DA:37,0 -DA:38,0 -DA:39,0 -DA:40,0 -DA:41,0 -DA:42,0 -DA:43,0 -DA:44,0 -DA:45,0 -DA:48,0 -DA:50,0 -DA:52,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:62,0 -DA:63,0 -DA:64,0 -DA:68,0 -DA:70,0 -DA:72,0 -DA:73,0 -DA:80,0 -DA:82,0 -DA:83,0 -DA:84,0 -DA:88,0 -DA:89,0 -DA:97,0 -DA:98,0 -DA:99,0 -DA:106,0 -DA:108,0 -DA:110,0 -DA:111,0 -DA:112,0 -DA:113,0 -DA:116,0 -DA:118,0 -DA:119,0 -DA:120,0 -DA:124,0 -DA:125,0 -DA:127,0 -DA:129,0 -DA:130,0 -DA:131,0 -DA:133,0 -DA:134,0 -DA:154,0 -DA:155,0 -DA:156,0 -DA:158,0 -DA:161,0 -DA:162,0 -LF:61 -LH:0 -end_of_record -SF:lib/features/instruments/details/instrument_details_providers.g.dart +SF:lib/core/theme/theme_mode_controller.g.dart DA:9,0 -DA:13,0 -DA:15,0 -DA:17,0 -DA:19,0 -DA:20,0 -DA:23,0 -DA:25,0 -DA:27,0 -DA:28,0 -DA:48,1 -DA:54,0 -DA:57,0 -DA:61,0 -DA:65,0 -DA:68,0 -DA:73,0 -DA:78,0 -DA:79,0 -DA:84,0 -DA:85,0 -DA:90,0 -DA:97,0 -DA:101,0 -DA:109,0 -DA:111,0 -DA:112,0 -DA:125,0 -DA:133,0 -DA:137,0 -DA:141,0 -DA:142,0 -DA:146,0 -DA:148,0 -DA:150,0 -DA:151,0 -DA:152,0 -DA:157,0 -DA:162,0 -DA:164,0 -DA:167,0 -DA:170,0 -DA:173,0 -DA:176,0 -DA:177,0 -DA:178,0 -DA:179,0 -DA:180,0 -DA:181,0 -DA:182,0 -DA:183,0 -DA:187,0 -DA:189,0 -DA:192,0 -DA:194,0 -DA:195,0 -DA:197,0 -DA:209,0 -DA:211,0 -DA:212,0 -LF:60 -LH:1 -end_of_record -SF:lib/features/instruments/details/instrument_details_providers.dart -DA:9,0 -DA:11,0 -DA:12,0 -DA:13,0 -DA:14,0 -DA:16,0 +DA:14,2 +DA:15,1 +DA:26,1 +DA:30,2 +DA:31,1 LF:6 -LH:0 -end_of_record -SF:lib/features/instruments/details/widgets/instrument_details_summary.dart -DA:5,0 -DA:12,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:18,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:23,0 -LF:11 -LH:0 -end_of_record -SF:lib/features/instruments/details/widgets/instrument_header_images.dart -DA:12,0 -DA:22,0 -DA:24,0 -DA:26,0 -DA:27,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:33,0 -DA:34,0 -DA:35,0 -DA:36,0 -DA:37,0 -DA:38,0 -DA:40,0 -DA:41,0 -DA:43,0 -DA:44,0 -DA:45,0 -DA:52,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:60,0 -DA:61,0 -DA:73,0 -DA:74,0 -DA:76,0 -DA:78,0 -DA:79,0 -DA:81,0 -DA:82,0 -DA:83,0 -DA:84,0 -DA:92,0 -DA:96,0 -DA:98,0 -DA:99,0 -DA:110,0 -DA:111,0 -DA:113,0 -DA:114,0 -DA:115,0 -DA:116,0 -DA:117,0 -DA:118,0 -DA:119,0 -DA:120,0 -DA:121,0 -DA:140,0 -DA:145,0 -DA:146,0 -DA:147,0 -DA:148,0 -DA:153,0 -LF:55 -LH:0 -end_of_record -SF:lib/features/instruments/instruments_tab_providers.dart -DA:11,0 -DA:13,0 -DA:16,0 -DA:17,0 -DA:18,0 -DA:19,0 -DA:20,0 -LF:7 -LH:0 -end_of_record -SF:lib/features/instruments/instruments_tab_providers.g.dart -DA:9,0 -DA:13,0 -LF:2 -LH:0 +LH:5 end_of_record diff --git a/lib/common_widgets/app_animated_linear_gradient.dart b/lib/common_widgets/app_animated_linear_gradient.dart index 49bb3b4..1745bee 100644 --- a/lib/common_widgets/app_animated_linear_gradient.dart +++ b/lib/common_widgets/app_animated_linear_gradient.dart @@ -22,21 +22,14 @@ class AppAnimatedLinearGradient extends StatefulWidget { class _AppAnimatedLinearGradientState extends State with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _animation; + late final _controller = + AnimationController(duration: widget.duration, vsync: this); + late final _animation = Tween(begin: 0, end: 1).animate(_controller); @override void initState() { super.initState(); - _controller = AnimationController(duration: widget.duration, vsync: this) - ..repeat(reverse: true); - _animation = Tween(begin: 0, end: 1).animate(_controller); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); + _controller.repeat(reverse: true); } @override @@ -54,19 +47,26 @@ class _AppAnimatedLinearGradientState extends State }, ); } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } } class LinearGradientPainter extends CustomPainter { + final List colors; + final double percent; + final Alignment begin; + final Alignment end; + LinearGradientPainter({ required this.colors, required this.percent, this.begin = Alignment.topLeft, this.end = Alignment.bottomRight, }); - final List colors; - final double percent; - final Alignment begin; - final Alignment end; @override void paint(Canvas canvas, Size size) { diff --git a/lib/common_widgets/app_animation_wrapper.dart b/lib/common_widgets/app_animation_wrapper.dart index 72b7d7c..1bb54c0 100644 --- a/lib/common_widgets/app_animation_wrapper.dart +++ b/lib/common_widgets/app_animation_wrapper.dart @@ -5,10 +5,15 @@ class AppAnimationWrapper extends StatefulWidget { required this.child, super.key, this.keepAlive = false, + this.duration = const Duration(milliseconds: 350), }); final bool keepAlive; final Widget child; + final Duration duration; + + static const start = 0.6; + static const end = 1.0; @override State createState() => _AppAnimationWrapperState(); @@ -16,21 +21,27 @@ class AppAnimationWrapper extends StatefulWidget { class _AppAnimationWrapperState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { - late final AnimationController _controller; - late final Animation _animation; + late final AnimationController _controller = AnimationController( + vsync: this, + duration: widget.duration, + )..forward(); + late final Animation _animation = Tween( + begin: AppAnimationWrapper.start, + end: AppAnimationWrapper.end, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + ), + ); @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 350), - )..forward(); - _animation = Tween(begin: 0.6, end: 1).animate( - CurvedAnimation( - parent: _controller, - curve: Curves.easeInOut, - ), + Widget build(BuildContext context) { + super.build(context); + + return ScaleTransition( + scale: _animation, + child: widget.child, ); } @@ -40,15 +51,6 @@ class _AppAnimationWrapperState extends State super.dispose(); } - @override - Widget build(BuildContext context) { - super.build(context); - return ScaleTransition( - scale: _animation, - child: widget.child, - ); - } - @override bool get wantKeepAlive => widget.keepAlive; } diff --git a/lib/common_widgets/app_async_widget.dart b/lib/common_widgets/app_async_widget.dart index 161deb8..8deec35 100644 --- a/lib/common_widgets/app_async_widget.dart +++ b/lib/common_widgets/app_async_widget.dart @@ -77,6 +77,7 @@ class _AppAsyncSliverWidgetState extends State> { @override Widget build(BuildContext context) { final isRefreshing = widget.asyncValue.isRefreshing; + return SliverAnimatedSwitcher( duration: kThemeAnimationDuration, child: switch (widget.asyncValue) { @@ -129,7 +130,7 @@ class _AppAsyncSliverWidgetState extends State> { Future errorRetry() async { setState(() => isLoading = true); - widget.onErrorRetry!(); + widget.onErrorRetry?.call(); await Future.delayed(const Duration(milliseconds: 700)); setState(() => isLoading = false); } diff --git a/lib/common_widgets/app_cupertino_button.dart b/lib/common_widgets/app_cupertino_button.dart index 87609ed..29f26d4 100644 --- a/lib/common_widgets/app_cupertino_button.dart +++ b/lib/common_widgets/app_cupertino_button.dart @@ -6,13 +6,6 @@ import '../core/extensions/theme_of_context_extension.dart'; ///extended CupertinoButton to pass null values in the minimumSize and padding -enum CupertinoButtonType { - plain, - gray, - tinted, - filled, -} - class AppCupertinoButton extends StatelessWidget { const AppCupertinoButton({ required this.child, @@ -24,7 +17,7 @@ class AppCupertinoButton extends StatelessWidget { this.padding = EdgeInsets.zero, this.minSize, this.borderRadius = const BorderRadius.all(Radius.circular(32)), - this.type, + this.type = CupertinoButtonType.plain, }); const AppCupertinoButton.tinted({ @@ -48,11 +41,14 @@ class AppCupertinoButton extends StatelessWidget { final Color? color; final Color? disabledColor; final BorderRadius borderRadius; - final CupertinoButtonType? type; + final CupertinoButtonType type; @override Widget build(BuildContext context) { - final typeSize = type == null ? 0.0 : kMinInteractiveDimensionCupertino; + final typeSize = type == CupertinoButtonType.plain + ? 0.0 + : kMinInteractiveDimensionCupertino; + return Theme( data: Theme.of(context).copyWith( cupertinoOverrideTheme: CupertinoThemeData( @@ -74,7 +70,7 @@ class AppCupertinoButton extends StatelessWidget { padding: padding, minSize: minSize ?? typeSize, borderRadius: borderRadius, - color: _calculateColor(context), + color: type.calculateColor(color, context), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -91,12 +87,24 @@ class AppCupertinoButton extends StatelessWidget { ), ); } +} + +enum CupertinoButtonType { + plain, + gray, + tinted, + filled; + + static const tintedOpacity = 0.2; - Color? _calculateColor(BuildContext context) { - return switch (type) { - CupertinoButtonType.plain || null => null, + Color? calculateColor( + Color? color, + BuildContext context, + ) { + return switch (this) { + CupertinoButtonType.plain => null, CupertinoButtonType.gray => CupertinoColors.systemFill, - CupertinoButtonType.tinted => color!.withOpacity(0.2), + CupertinoButtonType.tinted => color?.withOpacity(tintedOpacity), CupertinoButtonType.filled => context.colorScheme.primary.darken() }; } diff --git a/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart b/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart index 5f59334..f5242da 100644 --- a/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart +++ b/lib/common_widgets/app_cupertino_sliver_navigation_bar.dart @@ -26,6 +26,7 @@ class AppCupertinoSliverNavigationBar extends StatelessWidget { @override Widget build(BuildContext context) { final screenSize = context.screenSize; + return CupertinoSliverNavigationBar( backgroundColor: Colors.transparent, largeTitle: Text( @@ -38,7 +39,7 @@ class AppCupertinoSliverNavigationBar extends StatelessWidget { transitionBetweenRoutes: transitionBetweenRoutes, border: Border( bottom: BorderSide( - color: context.customColors.inverseTextColor!.withOpacity(0.1), + color: context.customInverseTextColor.withOpacity(0.1), ), ), padding: kIsWeb diff --git a/lib/common_widgets/app_fade_in_image.dart b/lib/common_widgets/app_fade_in_image.dart index e76e8a5..6a637d0 100644 --- a/lib/common_widgets/app_fade_in_image.dart +++ b/lib/common_widgets/app_fade_in_image.dart @@ -44,6 +44,7 @@ class AppFadeInImage extends StatelessWidget { imageErrorBuilder: imageErrorBuilder ?? (context, error, stackTrace) { logViews.info('Error loading image:', error, stackTrace); + return AppErrorImageBuilder( height: height, textColor: context.colorScheme.onErrorContainer, diff --git a/lib/common_widgets/app_image.dart b/lib/common_widgets/app_image.dart deleted file mode 100644 index a008711..0000000 --- a/lib/common_widgets/app_image.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:extended_image/extended_image.dart'; -import 'package:flutter/material.dart'; - -class AppImageNetwork extends StatelessWidget { - const AppImageNetwork( - this.imageUrl, { - super.key, - }); - final String imageUrl; - - @override - Widget build(BuildContext context) { - return Image( - fit: BoxFit.cover, - image: ExtendedNetworkImageProvider( - imageUrl, - cache: true, - ), - ); - } -} diff --git a/lib/common_widgets/app_infinite_rotation_animation.dart b/lib/common_widgets/app_infinite_rotation_animation.dart index d720711..86018f5 100644 --- a/lib/common_widgets/app_infinite_rotation_animation.dart +++ b/lib/common_widgets/app_infinite_rotation_animation.dart @@ -19,14 +19,16 @@ class AppInfiniteRotationAnimation extends StatefulWidget { class _AppInfiniteRotationAnimationState extends State with SingleTickerProviderStateMixin { - late AnimationController _controller; + late final _controller = AnimationController( + duration: widget.duration, + vsync: this, + ); @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: widget.duration, - vsync: this, + Widget build(BuildContext context) { + return RotationTransition( + turns: _controller, + child: widget.child, ); } @@ -47,12 +49,4 @@ class _AppInfiniteRotationAnimationState _controller.dispose(); super.dispose(); } - - @override - Widget build(BuildContext context) { - return RotationTransition( - turns: _controller, - child: widget.child, - ); - } } diff --git a/lib/common_widgets/app_loading_indicator.dart b/lib/common_widgets/app_loading_indicator.dart index 188c040..56975b6 100644 --- a/lib/common_widgets/app_loading_indicator.dart +++ b/lib/common_widgets/app_loading_indicator.dart @@ -34,6 +34,7 @@ class AppLoadingIndicator extends StatelessWidget { ), ); } + return AnimatedSize( duration: const Duration(milliseconds: 300), child: !showLoading diff --git a/lib/common_widgets/app_web_padding.dart b/lib/common_widgets/app_web_padding.dart index fa552d4..1eafb27 100644 --- a/lib/common_widgets/app_web_padding.dart +++ b/lib/common_widgets/app_web_padding.dart @@ -41,29 +41,18 @@ class AppWebPadding extends StatelessWidget { @override Widget build(BuildContext context) { if (kIsWeb) { - return color != null - ? ColoredBox( - color: color!, - child: Padding( - padding: EdgeInsets.only( - bottom: bottom ? bottomInset() : 0, - top: top ? topInset() : 0, - left: left ? leftInset() : 0, - right: right ? rightInset() : 0, - ), - child: child, - ), - ) - : Padding( - padding: EdgeInsets.only( - bottom: bottom ? bottomInset() : 0, - top: top ? topInset() : 0, - left: left ? leftInset() : 0, - right: right ? rightInset() : 0, - ), - child: child, - ); + return Container( + color: color, + padding: EdgeInsets.only( + bottom: bottom ? bottomInset() : 0, + top: top ? topInset() : 0, + left: left ? leftInset() : 0, + right: right ? rightInset() : 0, + ), + child: child, + ); } + return child; } } @@ -101,29 +90,20 @@ class WebPaddingSliver extends StatelessWidget { @override Widget build(BuildContext context) { if (kIsWeb) { - return color != null - ? DecoratedSliver( - decoration: BoxDecoration(color: color), - sliver: SliverPadding( - padding: EdgeInsets.only( - bottom: bottom ? bottomInset() : 0, - top: top ? topInset() : 0, - left: left ? leftInset() : 0, - right: right ? rightInset() : 0, - ), - sliver: sliver, - ), - ) - : SliverPadding( - padding: EdgeInsets.only( - bottom: bottom ? bottomInset() : 0, - top: top ? topInset() : 0, - left: left ? leftInset() : 0, - right: right ? rightInset() : 0, - ), - sliver: sliver, - ); + DecoratedSliver( + decoration: BoxDecoration(color: color), + sliver: SliverPadding( + padding: EdgeInsets.only( + bottom: bottom ? bottomInset() : 0, + top: top ? topInset() : 0, + left: left ? leftInset() : 0, + right: right ? rightInset() : 0, + ), + sliver: sliver, + ), + ); } + return sliver; } } diff --git a/lib/common_widgets/cupertino_sheet.dart b/lib/common_widgets/cupertino_sheet_route.dart similarity index 98% rename from lib/common_widgets/cupertino_sheet.dart rename to lib/common_widgets/cupertino_sheet_route.dart index 94205ed..7213e76 100644 --- a/lib/common_widgets/cupertino_sheet.dart +++ b/lib/common_widgets/cupertino_sheet_route.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_non_null_assertion, avoid_returning_widgets, +// ignore_for_file: avoid_unnecessary_type_assertions // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -62,6 +64,7 @@ class _CupertinoSheetDecorationBuilder extends StatelessWidget { @override Widget build(BuildContext context) { final topPadding = MediaQuery.paddingOf(context).top; + return SafeArea( bottom: false, minimum: EdgeInsets.only(top: topPadding + _kPreviousRouteVisibleOffset), @@ -103,6 +106,14 @@ class _CupertinoSheetDecorationBuilder extends StatelessWidget { /// /// * [AppCupertinoSheetPage], which is the [Page] version of this class class CupertinoSheetRoute extends SheetRoute { + final SheetController _sheetController = SheetController(); + + @override + Color? get barrierColor => Colors.transparent; + + @override + bool get barrierDismissible => true; + CupertinoSheetRoute({ required WidgetBuilder builder, super.stops, @@ -124,19 +135,11 @@ class CupertinoSheetRoute extends SheetRoute { initialExtent: initialStop, ); - final SheetController _sheetController = SheetController(); - @override SheetController createSheetController() { return _sheetController; } - @override - Color? get barrierColor => Colors.transparent; - - @override - bool get barrierDismissible => true; - @override Widget buildSheet(BuildContext context, Widget child) { SheetPhysics? effectivePhysics = BouncingSheetPhysics( @@ -152,6 +155,7 @@ class CupertinoSheetRoute extends SheetRoute { final topPadding = MediaQuery.paddingOf(context).top; final topMargin = math.max(_kSheetMinimalOffset, topPadding) + _kPreviousRouteVisibleOffset; + return Sheet.raw( initialExtent: initialExtent, decorationBuilder: decorationBuilder, @@ -172,6 +176,7 @@ class CupertinoSheetRoute extends SheetRoute { ) { final topPadding = MediaQuery.paddingOf(context).top; final double topOffset = math.max(_kSheetMinimalOffset, topPadding); + return AnimatedBuilder( animation: secondaryAnimation, child: CupertinoUserInterfaceLevel( @@ -184,6 +189,7 @@ class CupertinoSheetRoute extends SheetRoute { final distanceWithScale = (topOffset + _kPreviousRouteVisibleOffset) * 0.9; final offset = Offset(0, progress * (topOffset - distanceWithScale)); + return Transform.translate( offset: offset, child: Transform.scale( @@ -252,6 +258,7 @@ class CupertinoSheetBottomRouteTransition extends StatelessWidget { // Round corners for iPhone devices from X to the newest version final isRoundedDevice = defaultTargetPlatform == TargetPlatform.iOS && topPadding > _kRoundedDeviceStatusBarHeight; + return isRoundedDevice ? _kRoundedDeviceRadius : Radius.zero; } @@ -277,6 +284,7 @@ class CupertinoSheetBottomRouteTransition extends StatelessWidget { final radius = progress == 0 ? Radius.zero : Radius.lerp(deviceCorner, _kCupertinoSheetTopRadius, progress)!; + return Stack( children: [ Container(color: CupertinoColors.black), @@ -323,6 +331,14 @@ class CupertinoSheetBottomRouteTransition extends StatelessWidget { /// /// * [CupertinoSheetRoute], which is the [PageRoute] version of this class class AppCupertinoSheetPage extends Page { + /// The content to be shown in the [Route] created by this page. + final Widget child; + + /// {@macro flutter.widgets.modalRoute.maintainState} + final bool maintainState; + + final SheetFit fit; + /// Creates a material page. const AppCupertinoSheetPage({ required this.child, @@ -333,14 +349,6 @@ class AppCupertinoSheetPage extends Page { super.arguments, }); - /// The content to be shown in the [Route] created by this page. - final Widget child; - - /// {@macro flutter.widgets.modalRoute.maintainState} - final bool maintainState; - - final SheetFit fit; - @override Route createRoute(BuildContext context) { return _PageBasedCupertinoSheetRoute(page: this); @@ -352,6 +360,13 @@ class AppCupertinoSheetPage extends Page { // This route uses the builder from the page to build its content. This ensures // the content is up to date after page updates. class _PageBasedCupertinoSheetRoute extends CupertinoSheetRoute { + AppCupertinoSheetPage get _page => settings as AppCupertinoSheetPage; + + @override + bool get maintainState => _page.maintainState; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; _PageBasedCupertinoSheetRoute({ required AppCupertinoSheetPage page, super.stops, @@ -367,12 +382,4 @@ class _PageBasedCupertinoSheetRoute extends CupertinoSheetRoute { .child; }, ); - - AppCupertinoSheetPage get _page => settings as AppCupertinoSheetPage; - - @override - bool get maintainState => _page.maintainState; - - @override - String get debugLabel => '${super.debugLabel}(${_page.name})'; } diff --git a/lib/constants.dart b/lib/constants.dart index 64143da..eeb608b 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1,8 +1,8 @@ -final class AppConstants { - AppConstants._(); - +final class Constants { // Network static const baseUrlPath = 'https://samba.deno.dev'; static const connectTimeout = Duration(seconds: 2); static const receiveTimeout = Duration(seconds: 3); + + Constants._(); } diff --git a/lib/core/app_provider.dart b/lib/core/app_provider.dart deleted file mode 100644 index 9e225fe..0000000 --- a/lib/core/app_provider.dart +++ /dev/null @@ -1,39 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'package:flutter/foundation.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'app_provider.g.dart'; - -@Riverpod(keepAlive: true) -class AppParent extends _$AppParent { - @override - FutureOr build() { - return const AppModel('AppParent'); - } -} - -@Riverpod(keepAlive: true) -class App extends _$App { - @override - FutureOr build() async { - await Future.delayed(const Duration(seconds: 1)); - final value = await ref.watch(appParentProvider.future); - return AppModel(value.name); - } -} - -@immutable -class AppModel { - const AppModel(this.name); - final String name; - - @override - bool operator ==(covariant AppModel other) { - if (identical(this, other)) return true; - - return other.name == name; - } - - @override - int get hashCode => name.hashCode; -} diff --git a/lib/core/app_provider.g.dart b/lib/core/app_provider.g.dart deleted file mode 100644 index 0da6b5b..0000000 --- a/lib/core/app_provider.g.dart +++ /dev/null @@ -1,38 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'app_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$appParentHash() => r'8a8deb01447fb521a815b00cc1d0c9c801200c65'; - -/// See also [AppParent]. -@ProviderFor(AppParent) -final appParentProvider = AsyncNotifierProvider.internal( - AppParent.new, - name: r'appParentProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$appParentHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$AppParent = AsyncNotifier; -String _$appHash() => r'498ba010bd10d9d3de19f6aefde4544f4737cd8e'; - -/// See also [App]. -@ProviderFor(App) -final appProvider = AsyncNotifierProvider.internal( - App.new, - name: r'appProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$appHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$App = AsyncNotifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/core/extensions/app_localization_extension.dart b/lib/core/extensions/app_localization_extension.dart index 23aeab6..3edff2a 100644 --- a/lib/core/extensions/app_localization_extension.dart +++ b/lib/core/extensions/app_localization_extension.dart @@ -2,6 +2,6 @@ import 'package:flutter/widgets.dart'; import '../../l10n/app_localizations.dart'; -extension LocalizedBuildContext on BuildContext { +extension AppLocalizationExtension on BuildContext { AppLocalizations get loc => AppLocalizations.of(this); } diff --git a/lib/core/extensions/context_snackbar.dart b/lib/core/extensions/context_snackbar.dart index e3d802f..efd2036 100644 --- a/lib/core/extensions/context_snackbar.dart +++ b/lib/core/extensions/context_snackbar.dart @@ -1,26 +1,20 @@ import 'package:flutter/material.dart'; -import 'theme_of_context_extension.dart'; - extension ContextSnackBar on BuildContext { - void showSnackBar({ + void showSnackBarText({ required Widget content, Key? key, Color? backgroundColor, double? elevation, - EdgeInsetsGeometry? margin, EdgeInsetsGeometry? padding, double? width, ShapeBorder? shape, - HitTestBehavior? hitTestBehavior, - SnackBarBehavior? behavior, }) { ScaffoldMessenger.of(this).clearSnackBars(); ScaffoldMessenger.of(this).showSnackBar( SnackBar( - margin: - margin ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - behavior: behavior ?? SnackBarBehavior.floating, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + behavior: SnackBarBehavior.floating, content: content, key: key, backgroundColor: backgroundColor, @@ -31,36 +25,4 @@ extension ContextSnackBar on BuildContext { ), ); } - - void showSnackBarText( - String text, { - Key? key, - Color? backgroundColor, - double? elevation, - EdgeInsetsGeometry? margin, - EdgeInsetsGeometry? padding, - double? width, - ShapeBorder? shape, - HitTestBehavior? hitTestBehavior, - SnackBarBehavior? behavior, - }) => - showSnackBar( - content: Text( - text, - style: textTheme.titleSmall!.copyWith( - color: colorScheme.onSurface, - fontWeight: FontWeight.w700, - fontStyle: FontStyle.italic, - ), - ), - key: key, - backgroundColor: backgroundColor, - elevation: elevation, - margin: margin, - padding: padding, - width: width, - shape: shape, - hitTestBehavior: hitTestBehavior, - behavior: behavior, - ); } diff --git a/lib/core/extensions/hardcoded_extension.dart b/lib/core/extensions/hardcoded_extension.dart index 10d4540..a60a151 100644 --- a/lib/core/extensions/hardcoded_extension.dart +++ b/lib/core/extensions/hardcoded_extension.dart @@ -1,4 +1,4 @@ -extension HardcodeExtension on String { +extension HardcodedExtension on String { /// Returns the string itself, works to add translation later String get hardcoded => this; } diff --git a/lib/core/extensions/intl_extension.dart b/lib/core/extensions/intl_extension.dart index 0470a51..842441c 100644 --- a/lib/core/extensions/intl_extension.dart +++ b/lib/core/extensions/intl_extension.dart @@ -27,6 +27,7 @@ extension IntlExtension on DateTime { Localizations.localeOf(context).languageCode, ).add_Hm().format(this); } + return DateFormat.yMMMd( Localizations.localeOf(context).languageCode, ).add_Hm().format(this); @@ -49,6 +50,7 @@ extension OrdinalExtension on int { }, (_) => '$this', }; + return spaceAfter ? '$ordinal ' : ordinal; } diff --git a/lib/core/extensions/router_extension.dart b/lib/core/extensions/router_extension.dart index 5443efc..cabee04 100644 --- a/lib/core/extensions/router_extension.dart +++ b/lib/core/extensions/router_extension.dart @@ -1,11 +1,12 @@ import 'package:go_router/go_router.dart'; -extension GoRouteExtension on GoRoute { +extension RouterExtension on GoRoute { String addPathParameters(Map parameters) { var newPath = path; for (final entry in parameters.entries) { newPath = newPath.replaceAll(':${entry.key}', entry.value); } + return newPath; } } diff --git a/lib/core/extensions/string_extension.dart b/lib/core/extensions/string_extensions.dart similarity index 93% rename from lib/core/extensions/string_extension.dart rename to lib/core/extensions/string_extensions.dart index de6ffe3..a2e02a2 100644 --- a/lib/core/extensions/string_extension.dart +++ b/lib/core/extensions/string_extensions.dart @@ -1,4 +1,4 @@ -extension EnumExtension on String { +extension StringExtensions on String { String get capitalize => '${this[0].toUpperCase()}${substring(1)}'; } diff --git a/lib/core/extensions/text_lines_extension.dart b/lib/core/extensions/text_lines_extension.dart new file mode 100644 index 0000000..99611de --- /dev/null +++ b/lib/core/extensions/text_lines_extension.dart @@ -0,0 +1,28 @@ +import 'package:flutter/widgets.dart'; + +const _defaultLineHeight = 20.0; + +extension TextLinesExtension on String { + int calculateLines(BuildContext context, {double? width, TextStyle? style}) { + final textPainter = TextPainter( + text: TextSpan( + text: this, + style: style ?? DefaultTextStyle.of(context).style, + ), + textDirection: TextDirection.ltr, + )..layout(maxWidth: width ?? MediaQuery.of(context).size.width); + + return textPainter.computeLineMetrics().length; + } + + double calculateHeightByLines( + BuildContext context, { + double? width, + TextStyle? style, + double paddingHeight = 0, + }) { + final lines = calculateLines(context, width: width, style: style); + + return (lines * (style?.height ?? _defaultLineHeight)) + paddingHeight; + } +} diff --git a/lib/core/extensions/theme_of_context_extension.dart b/lib/core/extensions/theme_of_context_extension.dart index 4c485de..f57f4ae 100644 --- a/lib/core/extensions/theme_of_context_extension.dart +++ b/lib/core/extensions/theme_of_context_extension.dart @@ -1,18 +1,40 @@ +// ignore_for_file: avoid_non_null_assertion import 'package:flutter/material.dart'; -import '../theme/theme_data.dart'; +import '../theme/app_theme.dart'; import 'app_localization_extension.dart'; -extension TextThemeOfContextExtension on BuildContext { - TextTheme get textTheme => Theme.of(this).textTheme; +extension ThemeOfContextExtension on BuildContext { + ThemeData get theme => Theme.of(this); - Brightness get brightness => Theme.of(this).brightness; + Brightness get brightness => theme.brightness; bool get brightnessIsDark => brightness == Brightness.dark; bool get brightnessIsLight => brightness == Brightness.light; - ColorScheme get colorScheme => Theme.of(this).colorScheme; - AppCustomColors get customColors => - Theme.of(this).extension()!; + ColorScheme get colorScheme => theme.colorScheme; + + // TextStyles non nullables https://github.com/flutter/flutter/issues/86807 + TextTheme get textTheme => theme.textTheme; + TextStyle get headlineLarge => textTheme.headlineLarge!; + TextStyle get headlineMedium => textTheme.headlineMedium!; + TextStyle get headlineSmall => textTheme.headlineSmall!; + TextStyle get titleSmall => textTheme.titleSmall!; + TextStyle get titleMedium => textTheme.titleMedium!; + TextStyle get titleLarge => textTheme.titleLarge!; + TextStyle get bodySmall => textTheme.bodySmall!; + TextStyle get bodyMedium => textTheme.bodyMedium!; + TextStyle get bodyLarge => textTheme.bodyLarge!; + TextStyle get labelLarge => textTheme.labelLarge!; + TextStyle get labelMedium => textTheme.labelMedium!; + TextStyle get labelSmall => textTheme.labelSmall!; + + // Custom colors non nullables https://github.com/flutter/flutter/issues/75695 + AppCustomColors get customColors => theme.extension()!; + Color get customTextColor => customColors.textColor!; + Color get customInverseTextColor => customColors.inverseTextColor!; + Color get customGoldColor => customColors.goldColor!; + Color get customSilverColor => customColors.silverColor!; + Color get customBronzeColor => customColors.bronzeColor!; } extension ThemeModeExtension on ThemeMode { diff --git a/lib/core/providers/client_network_provider.dart b/lib/core/providers/client_network.dart similarity index 88% rename from lib/core/providers/client_network_provider.dart rename to lib/core/providers/client_network.dart index 9818f00..f850e26 100644 --- a/lib/core/providers/client_network_provider.dart +++ b/lib/core/providers/client_network.dart @@ -8,19 +8,20 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../constants.dart'; import '../../localization/language.dart'; -import '../../localization/language_app_provider.dart'; +import '../../localization/language_app_controller.dart'; import '../../utils/app_loggers.dart'; import 'client_network/network_client_adapter.dart' if (dart.library.js_interop) 'client_network/network_client_adapter_web.dart'; -part 'client_network_provider.g.dart'; +part 'client_network.g.dart'; @Riverpod(keepAlive: true) class ClientNetwork extends _$ClientNetwork { @override FutureOr build() async { final cacheDirPath = await _getTemporaryDirectory(); - final futureLanguage = await ref.watch(languageAppProvider.future); + final futureLanguage = + await ref.watch(languageAppControllerProvider.future); final language = futureLanguage.languageCode; final cache = CacheOptions( store: BackupCacheStore( @@ -31,12 +32,12 @@ class ClientNetwork extends _$ClientNetwork { ); final options = BaseOptions( baseUrl: Endpoint.basePath.path, - connectTimeout: AppConstants.connectTimeout, - receiveTimeout: AppConstants.receiveTimeout, + connectTimeout: Constants.connectTimeout, + receiveTimeout: Constants.receiveTimeout, queryParameters: {'language': language}, ); final dio = Dio(options) - ..httpClientAdapter = getNativeAdapter(cronetHttp2: true) + ..httpClientAdapter = getNativeAdapter() ..interceptors.add(DioCacheInterceptor(options: cache)) ..interceptors.add( LogInterceptor( @@ -60,9 +61,11 @@ class ClientNetwork extends _$ClientNetwork { Future _getTemporaryDirectory() async { try { final dir = await getTemporaryDirectory(); + return dir.path; } catch (e) { logNetwork.info('Error getting temporary directory: $e'); + return null; } } @@ -77,7 +80,7 @@ enum Endpoint { String get pathId => '$path/'; String get pathSearch => '$path/search'; String get path => switch (this) { - basePath => AppConstants.baseUrlPath, + basePath => Constants.baseUrlPath, parades => '/parades', instruments => '/instruments', schools => '/schools', @@ -85,18 +88,19 @@ enum Endpoint { } class AppNetworkError extends Error { + final String message; + AppNetworkError(this.message); AppNetworkError.fromNetworkClientException(Object e) : message = messageFromDio(e); - final String message; - @override String toString() => message; static String messageFromDio(Object e) { if (e is! DioException) return 'Unknown error 🤷'; + return switch (e.type) { DioExceptionType.badCertificate => 'Bad certificate 📜', DioExceptionType.connectionTimeout => 'Connection timeout ⏰', diff --git a/lib/core/providers/client_network_provider.g.dart b/lib/core/providers/client_network.g.dart similarity index 89% rename from lib/core/providers/client_network_provider.g.dart rename to lib/core/providers/client_network.g.dart index 581cab0..f303266 100644 --- a/lib/core/providers/client_network_provider.g.dart +++ b/lib/core/providers/client_network.g.dart @@ -1,12 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'client_network_provider.dart'; +part of 'client_network.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$clientNetworkHash() => r'5fb5d2868271a3c2c56090afd1ea4090c88252d3'; +String _$clientNetworkHash() => r'355f8709de49a11d80859fabaf293746bafcec1f'; /// See also [ClientNetwork]. @ProviderFor(ClientNetwork) diff --git a/lib/core/providers/client_network/network_client_adapter.dart b/lib/core/providers/client_network/network_client_adapter.dart index 7202933..10d18ea 100644 --- a/lib/core/providers/client_network/network_client_adapter.dart +++ b/lib/core/providers/client_network/network_client_adapter.dart @@ -2,13 +2,14 @@ import 'package:native_dio_adapter/native_dio_adapter.dart'; const _maxCacheSize = 2 * 1024 * 1024; // 2MB -NativeAdapter getNativeAdapter({required bool cronetHttp2}) { +NativeAdapter getNativeAdapter() { return NativeAdapter( createCupertinoConfiguration: () { final config = URLSessionConfiguration.ephemeralSessionConfiguration() ..cache = URLCache.withCapacity( memoryCapacity: _maxCacheSize, ); + return config; }, createCronetEngine: () { diff --git a/lib/core/providers/client_network/network_client_adapter_web.dart b/lib/core/providers/client_network/network_client_adapter_web.dart index 4e9da46..924afe9 100644 --- a/lib/core/providers/client_network/network_client_adapter_web.dart +++ b/lib/core/providers/client_network/network_client_adapter_web.dart @@ -7,5 +7,6 @@ import 'package:fetch_client/fetch_client.dart'; // ignore: implementation_imports import 'package:native_dio_adapter/src/conversion_layer_adapter.dart'; -HttpClientAdapter getNativeAdapter({bool? cronetHttp2}) => +// ignore: avoid_unused_parameters +HttpClientAdapter getNativeAdapter() => ConversionLayerAdapter(FetchClient(mode: RequestMode.cors)); diff --git a/lib/core/providers/initialization_provider.dart b/lib/core/providers/initialization.dart similarity index 89% rename from lib/core/providers/initialization_provider.dart rename to lib/core/providers/initialization.dart index 452bdab..c3291d3 100644 --- a/lib/core/providers/initialization_provider.dart +++ b/lib/core/providers/initialization.dart @@ -5,9 +5,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../utils/app_error_handler.dart'; import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; -import 'prefs_provider.dart'; +import 'prefs.dart'; -part 'initialization_provider.g.dart'; +part 'initialization.g.dart'; @Riverpod(keepAlive: true) Future initialization(InitializationRef ref) async { diff --git a/lib/core/providers/initialization_provider.g.dart b/lib/core/providers/initialization.g.dart similarity index 96% rename from lib/core/providers/initialization_provider.g.dart rename to lib/core/providers/initialization.g.dart index 399831e..09c9ee7 100644 --- a/lib/core/providers/initialization_provider.g.dart +++ b/lib/core/providers/initialization.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'initialization_provider.dart'; +part of 'initialization.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/lib/core/providers/prefs_provider.dart b/lib/core/providers/prefs.dart similarity index 56% rename from lib/core/providers/prefs_provider.dart rename to lib/core/providers/prefs.dart index 00da851..3efbc63 100644 --- a/lib/core/providers/prefs_provider.dart +++ b/lib/core/providers/prefs.dart @@ -1,8 +1,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; -part 'prefs_provider.g.dart'; +part 'prefs.g.dart'; @Riverpod(keepAlive: true) -Future prefs(PrefsRef ref) => - SharedPreferences.getInstance(); +Future prefs(PrefsRef _) { + return SharedPreferences.getInstance(); +} diff --git a/lib/core/providers/prefs_provider.g.dart b/lib/core/providers/prefs.g.dart similarity index 90% rename from lib/core/providers/prefs_provider.g.dart rename to lib/core/providers/prefs.g.dart index d9d911d..cd1432f 100644 --- a/lib/core/providers/prefs_provider.g.dart +++ b/lib/core/providers/prefs.g.dart @@ -1,12 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'prefs_provider.dart'; +part of 'prefs.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$prefsHash() => r'e8538273305ad075d4c7d66aa5893f862d51be3f'; +String _$prefsHash() => r'5fa192c20a47a46153bec9274d10ff8a5807f867'; /// See also [prefs]. @ProviderFor(prefs) diff --git a/lib/core/theme/theme_data.dart b/lib/core/theme/app_theme.dart similarity index 98% rename from lib/core/theme/theme_data.dart rename to lib/core/theme/app_theme.dart index f7d6bd9..ee58245 100644 --- a/lib/core/theme/theme_data.dart +++ b/lib/core/theme/app_theme.dart @@ -3,6 +3,8 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +const _iOSFontLetterSpacing = -1.5; + class AppTheme { static ThemeData get lightTheme => FlexThemeData.light( useMaterial3: true, @@ -79,7 +81,7 @@ class AppTheme { .navLargeTitleTextStyle // fixes a small bug with spacing .copyWith( - letterSpacing: -1.5, + letterSpacing: _iOSFontLetterSpacing, ), ), ); @@ -134,6 +136,7 @@ class AppCustomColors extends ThemeExtension { if (other is! AppCustomColors) { return this; } + return AppCustomColors( textColor: Color.lerp(textColor, other.textColor, t), inverseTextColor: Color.lerp(inverseTextColor, other.inverseTextColor, t), diff --git a/lib/core/theme/theme_provider.dart b/lib/core/theme/theme_mode_controller.dart similarity index 85% rename from lib/core/theme/theme_provider.dart rename to lib/core/theme/theme_mode_controller.dart index 86dab3a..db961d7 100644 --- a/lib/core/theme/theme_provider.dart +++ b/lib/core/theme/theme_mode_controller.dart @@ -2,39 +2,18 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../utils/app_loggers.dart'; -import '../providers/prefs_provider.dart'; +import '../providers/prefs.dart'; -part 'theme_provider.g.dart'; - -@riverpod -class AppThemeTrueBlack extends _$AppThemeTrueBlack { - @override - bool build() { - try { - return ref.watch(prefsProvider).value!.getBool('true_black') ?? false; - } catch (e) { - logViews.finest('$e'); - return false; - } - } - - void toggleTrueBlack({bool? forceState}) { - final prefs = ref.read(prefsProvider).requireValue; - state = forceState ?? !state; - if (state) { - prefs.setBool('true_black', state); - } else { - prefs.remove('true_black'); - } - } -} +part 'theme_mode_controller.g.dart'; @Riverpod(keepAlive: true) -class AppThemeMode extends _$AppThemeMode { +class ThemeModeController extends _$ThemeModeController { @override ThemeMode build() { try { - final mode = ref.watch(prefsProvider).value!.getString('theme_mode'); + final prefs = ref.watch(prefsProvider).value; + final mode = prefs?.getString('theme_mode'); + return switch (mode) { 'light' => ThemeMode.light, 'dark' => ThemeMode.dark, @@ -42,6 +21,7 @@ class AppThemeMode extends _$AppThemeMode { }; } catch (e) { logViews.finest('$e'); + return ThemeMode.system; } } @@ -81,3 +61,28 @@ class AppThemeMode extends _$AppThemeMode { } } } + +@riverpod +class AppThemeTrueBlack extends _$AppThemeTrueBlack { + @override + bool build() { + try { + return ref.watch(prefsProvider).requireValue.getBool('true_black') ?? + false; + } catch (e) { + logViews.finest('$e'); + + return false; + } + } + + void toggleTrueBlack({bool? forceState}) { + final prefs = ref.read(prefsProvider).requireValue; + state = forceState ?? !state; + if (state) { + prefs.setBool('true_black', state); + } else { + prefs.remove('true_black'); + } + } +} diff --git a/lib/core/theme/theme_provider.g.dart b/lib/core/theme/theme_mode_controller.g.dart similarity index 63% rename from lib/core/theme/theme_provider.g.dart rename to lib/core/theme/theme_mode_controller.g.dart index 0642824..79ac38e 100644 --- a/lib/core/theme/theme_provider.g.dart +++ b/lib/core/theme/theme_mode_controller.g.dart @@ -1,12 +1,29 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'theme_provider.dart'; +part of 'theme_mode_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$appThemeTrueBlackHash() => r'45e4f39626cbefaed196f5d2a35c786af909cab9'; +String _$themeModeControllerHash() => + r'dff5d6a2e47b086ae84bc400b4308dbce1143d7e'; + +/// See also [ThemeModeController]. +@ProviderFor(ThemeModeController) +final themeModeControllerProvider = + NotifierProvider.internal( + ThemeModeController.new, + name: r'themeModeControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$themeModeControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ThemeModeController = Notifier; +String _$appThemeTrueBlackHash() => r'be89cfe33db2954b047f1644c4b5af256171b836'; /// See also [AppThemeTrueBlack]. @ProviderFor(AppThemeTrueBlack) @@ -22,19 +39,5 @@ final appThemeTrueBlackProvider = ); typedef _$AppThemeTrueBlack = AutoDisposeNotifier; -String _$appThemeModeHash() => r'f7bbd1bb2bb4b87989403e6b45e606a1f3efc952'; - -/// See also [AppThemeMode]. -@ProviderFor(AppThemeMode) -final appThemeModeProvider = NotifierProvider.internal( - AppThemeMode.new, - name: r'appThemeModeProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$appThemeModeHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$AppThemeMode = Notifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart index adee111..d23154b 100644 --- a/lib/features/home/home_page.dart +++ b/lib/features/home/home_page.dart @@ -19,6 +19,7 @@ class HomePage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final screenSize = context.screenSize; + return Scaffold( body: screenSize.isSmall ? navigationShell diff --git a/lib/features/home/home_page_controller.dart b/lib/features/home/home_page_controller.dart index 1e38e1f..2f123c7 100644 --- a/lib/features/home/home_page_controller.dart +++ b/lib/features/home/home_page_controller.dart @@ -8,7 +8,7 @@ import '../schools/schools_tab_page.dart'; part 'home_page_controller.g.dart'; @Riverpod(keepAlive: true) -class CurrentTab extends _$CurrentTab { +class HomePageController extends _$HomePageController { @override ///Change for a pattern or record of hometab and boolean is is top diff --git a/lib/features/home/home_page_controller.g.dart b/lib/features/home/home_page_controller.g.dart index 3b8d3e0..9c8c691 100644 --- a/lib/features/home/home_page_controller.g.dart +++ b/lib/features/home/home_page_controller.g.dart @@ -6,20 +6,22 @@ part of 'home_page_controller.dart'; // RiverpodGenerator // ************************************************************************** -String _$currentTabHash() => r'2442f2e51bdc146b1b868464167636c0b2f2e3fa'; +String _$homePageControllerHash() => + r'df7d702c78f616ab4d2965468a89774e7e551e54'; -/// See also [CurrentTab]. -@ProviderFor(CurrentTab) -final currentTabProvider = - NotifierProvider.internal( - CurrentTab.new, - name: r'currentTabProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$currentTabHash, +/// See also [HomePageController]. +@ProviderFor(HomePageController) +final homePageControllerProvider = NotifierProvider.internal( + HomePageController.new, + name: r'homePageControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$homePageControllerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$CurrentTab = Notifier<({HomeTab tab, bool topRoute})>; +typedef _$HomePageController = Notifier<({HomeTab tab, bool topRoute})>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/home/widgets/adaptive_navigation_bar.dart b/lib/features/home/widgets/adaptive_navigation_bar.dart index 411b91a..10e5ed0 100644 --- a/lib/features/home/widgets/adaptive_navigation_bar.dart +++ b/lib/features/home/widgets/adaptive_navigation_bar.dart @@ -22,9 +22,10 @@ class AdaptiveNavigationBar extends StatelessWidget { @override Widget build(BuildContext context) { + const maxScaleFactor = 1.8; if (kIsCupertino) { return MediaQuery.withClampedTextScaling( - maxScaleFactor: 1.8, + maxScaleFactor: maxScaleFactor, child: AnimatedTheme( data: Theme.of(context), child: AppWebPadding.only( @@ -48,6 +49,7 @@ class AdaptiveNavigationBar extends StatelessWidget { ), ); } + return AppWebPadding.only( color: context.colorScheme.primaryContainer, bottom: true, diff --git a/lib/features/home/widgets/adaptive_navigation_rail.dart b/lib/features/home/widgets/adaptive_navigation_rail.dart index 89dd1c2..c694c7c 100644 --- a/lib/features/home/widgets/adaptive_navigation_rail.dart +++ b/lib/features/home/widgets/adaptive_navigation_rail.dart @@ -28,6 +28,7 @@ class AdaptiveNavigationRail extends StatelessWidget { final footerHeight = height <= ScreenSize.smallHeight ? AdaptiveNavigationRailFooter.heightCollapsed : AdaptiveNavigationRailFooter.heightFull; + return ColoredBox( color: context.colorScheme.surface, child: Column( diff --git a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart index a0f6986..b8e98b4 100644 --- a/lib/features/home/widgets/adaptive_navigation_rail_footer.dart +++ b/lib/features/home/widgets/adaptive_navigation_rail_footer.dart @@ -5,9 +5,9 @@ import 'package:pull_down_button/pull_down_button.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; -import '../../../core/theme/theme_provider.dart'; +import '../../../core/theme/theme_mode_controller.dart'; import '../../../localization/language.dart'; -import '../../../localization/language_app_provider.dart'; +import '../../../localization/language_app_controller.dart'; import '../../../utils/screen_size.dart'; import 'settings_modal_sheet.dart'; @@ -21,135 +21,150 @@ class AdaptiveNavigationRailFooter extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final locale = WidgetsBinding.instance.platformDispatcher.locale; final screenSize = context.screenSize; - final themeMode = ref.watch(appThemeModeProvider); + final themeMode = ref.watch(themeModeControllerProvider); final trueBlack = ref.watch(appThemeTrueBlackProvider); - if (MediaQuery.sizeOf(context).height < ScreenSize.smallHeight) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - InkWell( - onTap: () => showSettingModalSheet( - context, - padding: const EdgeInsets.symmetric(vertical: 24), - ), - child: CupertinoListTile.notched( - title: screenSize.isLarge - ? Text( - context.loc.settingsTitle, - style: context.textTheme.titleMedium, - ) - : const Icon(CupertinoIcons.settings), - leading: screenSize.isLarge ? Icon(themeMode.icon) : null, - ), - ), - ], - ); + + if (context.isSmallHeight) { + return AdaptiveRailSmallHeight(isLarge: false, themeMode: themeMode); } + return Column( - mainAxisAlignment: MainAxisAlignment.end, children: [ - PullDownButton( - scrollController: ScrollController(), - itemBuilder: (context) => [ - for (final language in Language.values) - PullDownMenuItem.selectable( - icon: language.languageCode == locale.languageCode - ? CupertinoIcons.device_phone_portrait - : null, - title: language.name(context), - subtitle: language.nativeName, - selected: language == ref.watch(languageAppProvider).value, - onTap: () { - ref.read(languageAppProvider.notifier).setLanguage(language); - }, - ), - ], - buttonBuilder: (context, showMenu) => CupertinoListTile.notched( - onTap: showMenu, - leading: screenSize.isLarge - ? Icon( - CupertinoIcons.flag, - color: context.colorScheme.onSurface, - ) - : null, - title: !screenSize.isLarge - ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Icon( - CupertinoIcons.flag, - color: context.colorScheme.onSurface, - ), - ) - : Text( - context.loc.language, - style: themeMode.isLight - ? context.textTheme.titleMedium! - .copyWith(color: Colors.grey) - : context.textTheme.titleMedium, - ), - ), + RailPullDown( + locale: locale, + themeMode: themeMode, + isLarge: screenSize.isLarge, ), InkWell( onTap: themeMode.isLight ? null - : () async => ref - .read(appThemeTrueBlackProvider.notifier) - .toggleTrueBlack(), + : ref.read(appThemeTrueBlackProvider.notifier).toggleTrueBlack, child: CupertinoListTile.notched( trailing: IgnorePointer( - child: screenSize.isMedium - ? null - : Switch.adaptive( - value: trueBlack, - applyCupertinoTheme: true, - onChanged: themeMode.isLight - ? null - : (_) => ref - .read(appThemeTrueBlackProvider.notifier) - .toggleTrueBlack(), - ), + child: Switch.adaptive( + value: trueBlack, + applyCupertinoTheme: true, + onChanged: themeMode.isLight + ? null + : (_) => ref + .read(appThemeTrueBlackProvider.notifier) + .toggleTrueBlack(), + ), + ), + leading: Icon( + CupertinoIcons.moon_stars, + color: themeMode.isLight + ? context.colorScheme.onSurface.withOpacity(0.5) + : context.colorScheme.onSurface, + ), + title: Text( + context.loc.themeTrueBlack, + style: themeMode.isLight + ? context.titleMedium.copyWith(color: Colors.grey) + : context.textTheme.titleMedium, ), - leading: screenSize.isLarge - ? Icon( - CupertinoIcons.moon_stars, - color: themeMode.isLight - ? context.colorScheme.onSurface.withOpacity(0.5) - : context.colorScheme.onSurface, - ) - : null, - title: screenSize.isMedium - ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Icon( - CupertinoIcons.moon_stars, - color: themeMode.isLight - ? context.colorScheme.onSurface.withOpacity(0.5) - : context.colorScheme.onSurface, - ), - ) - : Text( - context.loc.themeTrueBlack, - style: themeMode.isLight - ? context.textTheme.titleMedium! - .copyWith(color: Colors.grey) - : context.textTheme.titleMedium, - ), ), ), InkWell( - onTap: () async => - ref.read(appThemeModeProvider.notifier).toggleTheme(), + onTap: ref.read(themeModeControllerProvider.notifier).toggleTheme, + child: CupertinoListTile.notched( + title: Text( + themeMode.label(context), + style: context.textTheme.titleMedium, + ), + leading: Icon(themeMode.icon), + ), + ), + ], + ); + } +} + +class RailPullDown extends ConsumerWidget { + const RailPullDown({ + required this.locale, + required this.themeMode, + required this.isLarge, + super.key, + }); + + final Locale locale; + final ThemeMode themeMode; + final bool isLarge; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PullDownButton( + scrollController: ScrollController(), + itemBuilder: (context) => [ + for (final language in Language.values) + PullDownMenuItem.selectable( + icon: language.languageCode == locale.languageCode + ? CupertinoIcons.device_phone_portrait + : null, + title: language.name(context), + subtitle: language.nativeName, + selected: + language == ref.watch(languageAppControllerProvider).value, + onTap: () { + ref + .read(languageAppControllerProvider.notifier) + .setLanguage(language); + }, + ), + ], + buttonBuilder: (context, showMenu) => CupertinoListTile.notched( + onTap: showMenu, + leading: isLarge + ? Icon( + CupertinoIcons.flag, + color: context.colorScheme.onSurface, + ) + : null, + title: !isLarge + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Icon( + CupertinoIcons.flag, + color: context.colorScheme.onSurface, + ), + ) + : Text( + context.loc.language, + style: themeMode.isLight + ? context.titleMedium.copyWith(color: Colors.grey) + : context.textTheme.titleMedium, + ), + ), + ); + } +} + +class AdaptiveRailSmallHeight extends StatelessWidget { + final bool isLarge; + final ThemeMode themeMode; + + const AdaptiveRailSmallHeight({ + required this.isLarge, + required this.themeMode, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () => showSettingModalSheet(context), child: CupertinoListTile.notched( - title: screenSize.isLarge + title: isLarge ? Text( - themeMode.label(context), + context.loc.settingsTitle, style: context.textTheme.titleMedium, ) - : Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Icon(themeMode.icon), - ), - leading: screenSize.isLarge ? Icon(themeMode.icon) : null, + : const Icon(CupertinoIcons.settings), + leading: isLarge ? Icon(themeMode.icon) : null, ), ), ], diff --git a/lib/features/home/widgets/settings_modal_sheet.dart b/lib/features/home/widgets/settings_modal_sheet.dart index 8e291e3..8024781 100644 --- a/lib/features/home/widgets/settings_modal_sheet.dart +++ b/lib/features/home/widgets/settings_modal_sheet.dart @@ -6,12 +6,11 @@ import '../../../common_widgets/app_cupertino_button.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../localization/language.dart'; -import '../../../localization/language_app_provider.dart'; +import '../../../localization/language_app_controller.dart'; import 'settings_theme_section.dart'; void showSettingModalSheet( BuildContext context, { - EdgeInsets padding = const EdgeInsets.only(top: 24), bool showAsDialog = true, }) { showModalBottomSheet( @@ -20,7 +19,20 @@ void showSettingModalSheet( useRootNavigator: true, showDragHandle: false, isScrollControlled: true, - builder: (context) => SingleChildScrollView( + builder: (context) => SettingsModalSheet(showAsDialog: showAsDialog), + ); +} + +class SettingsModalSheet extends StatelessWidget { + final bool showAsDialog; + const SettingsModalSheet({ + super.key, + this.showAsDialog = true, + }); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( padding: const EdgeInsets.only(bottom: 32), controller: PrimaryScrollController.of(context), physics: const ClampingScrollPhysics(), @@ -31,7 +43,7 @@ void showSettingModalSheet( automaticallyImplyLeading: false, elevation: 0, title: Text(context.loc.settingsTitle), - titleTextStyle: context.textTheme.titleLarge!.copyWith( + titleTextStyle: context.titleLarge.copyWith( fontWeight: FontWeight.bold, ), backgroundColor: @@ -60,8 +72,8 @@ void showSettingModalSheet( const SettingsLanguageSection(), ], ), - ), - ); + ); + } } class SettingsLanguageSection extends ConsumerWidget { @@ -86,13 +98,15 @@ class SettingsLanguageSection extends ConsumerWidget { CupertinoListTile.notched( backgroundColor: context.colorScheme.surface, leading: Icon( - ref.watch(languageAppProvider).value == language + ref.watch(languageAppControllerProvider).value == language ? CupertinoIcons.check_mark_circled : null, color: context.colorScheme.primary, ), onTap: () { - ref.read(languageAppProvider.notifier).setLanguage(language); + ref + .read(languageAppControllerProvider.notifier) + .setLanguage(language); }, title: Text( language.nativeName, diff --git a/lib/features/home/widgets/settings_theme_section.dart b/lib/features/home/widgets/settings_theme_section.dart index 6681928..0ecc159 100644 --- a/lib/features/home/widgets/settings_theme_section.dart +++ b/lib/features/home/widgets/settings_theme_section.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; -import '../../../core/theme/theme_provider.dart'; +import '../../../core/theme/theme_mode_controller.dart'; class SettingsThemeSection extends ConsumerWidget { const SettingsThemeSection({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final themeMode = ref.watch(appThemeModeProvider); + final themeMode = ref.watch(themeModeControllerProvider); final trueBlack = ref.watch(appThemeTrueBlackProvider); return CupertinoListSection.insetGrouped( @@ -30,10 +30,10 @@ class SettingsThemeSection extends ConsumerWidget { title: CupertinoSegmentedControl( padding: EdgeInsets.zero, // This represents a currently selected segmented control. - groupValue: ref.watch(appThemeModeProvider), + groupValue: ref.watch(themeModeControllerProvider), // Callback that sets the selected segmented control. onValueChanged: (ThemeMode value) { - ref.read(appThemeModeProvider.notifier).setTheme(value); + ref.read(themeModeControllerProvider.notifier).setTheme(value); }, children: { ThemeMode.light: Padding( diff --git a/lib/features/instruments/details/instrument_details_providers.dart b/lib/features/instruments/details/instrument_details_controller.dart similarity index 62% rename from lib/features/instruments/details/instrument_details_providers.dart rename to lib/features/instruments/details/instrument_details_controller.dart index 46af2ad..e5a2cfd 100644 --- a/lib/features/instruments/details/instrument_details_providers.dart +++ b/lib/features/instruments/details/instrument_details_controller.dart @@ -1,20 +1,21 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../instrument.dart'; -import '../instruments_tab_providers.dart'; +import '../instruments_tab_controller.dart'; -part 'instrument_details_providers.g.dart'; +part 'instrument_details_controller.g.dart'; @riverpod -class InstrumentDetails extends _$InstrumentDetails { +class InstrumentDetailsController extends _$InstrumentDetailsController { @override Instrument build(int id) { final instrument = ref - .watch(instrumentsTabProvider) + .watch(instrumentsTabControllerProvider) .value ?.firstWhere((element) => element.id == id); if (instrument == null) { throw Exception('Instrument not found'); } + return instrument; } } diff --git a/lib/features/instruments/details/instrument_details_providers.g.dart b/lib/features/instruments/details/instrument_details_controller.g.dart similarity index 53% rename from lib/features/instruments/details/instrument_details_providers.g.dart rename to lib/features/instruments/details/instrument_details_controller.g.dart index b7c62f9..e587770 100644 --- a/lib/features/instruments/details/instrument_details_providers.g.dart +++ b/lib/features/instruments/details/instrument_details_controller.g.dart @@ -1,12 +1,13 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'instrument_details_providers.dart'; +part of 'instrument_details_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$instrumentDetailsHash() => r'8cd61b14528469a50013bae24788ac4938ee68b7'; +String _$instrumentDetailsControllerHash() => + r'62aacbc96a4ab812e1f78ce8707a195dfd47d1d7'; /// Copied from Dart SDK class _SystemHash { @@ -29,7 +30,7 @@ class _SystemHash { } } -abstract class _$InstrumentDetails +abstract class _$InstrumentDetailsController extends BuildlessAutoDisposeNotifier { late final int id; @@ -38,14 +39,14 @@ abstract class _$InstrumentDetails ); } -/// See also [InstrumentDetails]. -@ProviderFor(InstrumentDetails) -const instrumentDetailsProvider = InstrumentDetailsFamily(); +/// See also [InstrumentDetailsController]. +@ProviderFor(InstrumentDetailsController) +const instrumentDetailsControllerProvider = InstrumentDetailsControllerFamily(); -/// See also [InstrumentDetails]. -class InstrumentDetailsFamily extends Family { - /// See also [InstrumentDetails]. - const InstrumentDetailsFamily(); +/// See also [InstrumentDetailsController]. +class InstrumentDetailsControllerFamily extends Family { + /// See also [InstrumentDetailsController]. + const InstrumentDetailsControllerFamily(); static const Iterable? _dependencies = null; @@ -59,21 +60,21 @@ class InstrumentDetailsFamily extends Family { _allTransitiveDependencies; @override - String? get name => r'instrumentDetailsProvider'; + String? get name => r'instrumentDetailsControllerProvider'; - /// See also [InstrumentDetails]. - InstrumentDetailsProvider call( + /// See also [InstrumentDetailsController]. + InstrumentDetailsControllerProvider call( int id, ) { - return InstrumentDetailsProvider( + return InstrumentDetailsControllerProvider( id, ); } @visibleForOverriding @override - InstrumentDetailsProvider getProviderOverride( - covariant InstrumentDetailsProvider provider, + InstrumentDetailsControllerProvider getProviderOverride( + covariant InstrumentDetailsControllerProvider provider, ) { return call( provider.id, @@ -81,48 +82,50 @@ class InstrumentDetailsFamily extends Family { } /// Enables overriding the behavior of this provider, no matter the parameters. - Override overrideWith(InstrumentDetails Function() create) { - return _$InstrumentDetailsFamilyOverride(this, create); + Override overrideWith(InstrumentDetailsController Function() create) { + return _$InstrumentDetailsControllerFamilyOverride(this, create); } } -class _$InstrumentDetailsFamilyOverride implements FamilyOverride { - _$InstrumentDetailsFamilyOverride(this.overriddenFamily, this.create); +class _$InstrumentDetailsControllerFamilyOverride implements FamilyOverride { + _$InstrumentDetailsControllerFamilyOverride( + this.overriddenFamily, this.create); - final InstrumentDetails Function() create; + final InstrumentDetailsController Function() create; @override - final InstrumentDetailsFamily overriddenFamily; + final InstrumentDetailsControllerFamily overriddenFamily; @override - InstrumentDetailsProvider getProviderOverride( - covariant InstrumentDetailsProvider provider, + InstrumentDetailsControllerProvider getProviderOverride( + covariant InstrumentDetailsControllerProvider provider, ) { return provider._copyWith(create); } } -/// See also [InstrumentDetails]. -class InstrumentDetailsProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [InstrumentDetails]. - InstrumentDetailsProvider( +/// See also [InstrumentDetailsController]. +class InstrumentDetailsControllerProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [InstrumentDetailsController]. + InstrumentDetailsControllerProvider( int id, ) : this._internal( - () => InstrumentDetails()..id = id, - from: instrumentDetailsProvider, - name: r'instrumentDetailsProvider', + () => InstrumentDetailsController()..id = id, + from: instrumentDetailsControllerProvider, + name: r'instrumentDetailsControllerProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$instrumentDetailsHash, - dependencies: InstrumentDetailsFamily._dependencies, + : _$instrumentDetailsControllerHash, + dependencies: InstrumentDetailsControllerFamily._dependencies, allTransitiveDependencies: - InstrumentDetailsFamily._allTransitiveDependencies, + InstrumentDetailsControllerFamily._allTransitiveDependencies, id: id, ); - InstrumentDetailsProvider._internal( + InstrumentDetailsControllerProvider._internal( super.create, { required super.name, required super.dependencies, @@ -136,7 +139,7 @@ class InstrumentDetailsProvider @override Instrument runNotifierBuild( - covariant InstrumentDetails notifier, + covariant InstrumentDetailsController notifier, ) { return notifier.build( id, @@ -144,10 +147,10 @@ class InstrumentDetailsProvider } @override - Override overrideWith(InstrumentDetails Function() create) { + Override overrideWith(InstrumentDetailsController Function() create) { return ProviderOverride( origin: this, - override: InstrumentDetailsProvider._internal( + override: InstrumentDetailsControllerProvider._internal( () => create()..id = id, from: from, name: null, @@ -165,15 +168,15 @@ class InstrumentDetailsProvider } @override - AutoDisposeNotifierProviderElement + AutoDisposeNotifierProviderElement createElement() { - return _InstrumentDetailsProviderElement(this); + return _InstrumentDetailsControllerProviderElement(this); } - InstrumentDetailsProvider _copyWith( - InstrumentDetails Function() create, + InstrumentDetailsControllerProvider _copyWith( + InstrumentDetailsController Function() create, ) { - return InstrumentDetailsProvider._internal( + return InstrumentDetailsControllerProvider._internal( () => create()..id = id, name: name, dependencies: dependencies, @@ -186,7 +189,7 @@ class InstrumentDetailsProvider @override bool operator ==(Object other) { - return other is InstrumentDetailsProvider && other.id == id; + return other is InstrumentDetailsControllerProvider && other.id == id; } @override @@ -198,18 +201,19 @@ class InstrumentDetailsProvider } } -mixin InstrumentDetailsRef on AutoDisposeNotifierProviderRef { +mixin InstrumentDetailsControllerRef + on AutoDisposeNotifierProviderRef { /// The parameter `id` of this provider. int get id; } -class _InstrumentDetailsProviderElement - extends AutoDisposeNotifierProviderElement - with InstrumentDetailsRef { - _InstrumentDetailsProviderElement(super.provider); +class _InstrumentDetailsControllerProviderElement + extends AutoDisposeNotifierProviderElement with InstrumentDetailsControllerRef { + _InstrumentDetailsControllerProviderElement(super.provider); @override - int get id => (origin as InstrumentDetailsProvider).id; + int get id => (origin as InstrumentDetailsControllerProvider).id; } // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/instruments/details/instrument_details_page.dart b/lib/features/instruments/details/instrument_details_page.dart index 34caa2a..1dfd4a6 100644 --- a/lib/features/instruments/details/instrument_details_page.dart +++ b/lib/features/instruments/details/instrument_details_page.dart @@ -6,34 +6,38 @@ import '../../../common_widgets/app_back_button.dart'; import '../../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; import '../../../common_widgets/app_web_padding.dart'; import '../../../core/extensions/app_localization_extension.dart'; +import '../../../core/extensions/text_lines_extension.dart'; import '../../../utils/screen_size.dart'; -import 'instrument_details_providers.dart'; +import '../instrument.dart'; +import 'instrument_details_controller.dart'; import 'widgets/instrument_details_summary.dart'; import 'widgets/instrument_header_images.dart'; typedef InstrumentId = int; -enum InstrumentDetailsTab { summary, learning } - class InstrumentDetailsPage extends ConsumerStatefulWidget { const InstrumentDetailsPage({required this.id, super.key}); final InstrumentId id; static const path = 'details/:id'; + static const bottomPaddingContent = 200.0; @override ConsumerState createState() => _InstrumentDetailsPageState(); } +enum InstrumentDetailsTab { summary, learning } + class _InstrumentDetailsPageState extends ConsumerState { final _controller = ScrollController(); @override Widget build(BuildContext context) { - final value = ref.watch(instrumentDetailsProvider(widget.id)); + final value = ref.watch(instrumentDetailsControllerProvider(widget.id)); final screenConstraint = ScreenSize.lg.value; const imageHeight = 80.0; + return DefaultTabController( length: InstrumentDetailsTab.values.length, child: Scaffold( @@ -114,16 +118,8 @@ class _InstrumentDetailsPageState extends ConsumerState { top: 8, ), sliver: SliverToBoxAdapter( - // TODO(hectorAguero): hardcoded to avoid overscroll child: SizedBox( - height: value.translatedDescription - .calculateLines( - context, - width: screenConstraint, - ) - .toDouble() * - 20 + - 200, + height: _height(value, context, screenConstraint), child: TabBarView( physics: const ClampingScrollPhysics(), children: [ @@ -148,17 +144,16 @@ class _InstrumentDetailsPageState extends ConsumerState { ), ); } -} -extension TextLinesExtension on String { - int calculateLines(BuildContext context, {double? width, TextStyle? style}) { - final textPainter = TextPainter( - text: TextSpan( - text: this, - style: style ?? DefaultTextStyle.of(context).style, - ), - textDirection: TextDirection.ltr, - )..layout(maxWidth: width ?? MediaQuery.of(context).size.width); - return textPainter.computeLineMetrics().length; + double _height( + Instrument value, + BuildContext context, + double screenConstraint, + ) { + return value.translatedDescription.calculateHeightByLines( + context, + width: screenConstraint, + paddingHeight: InstrumentDetailsPage.bottomPaddingContent, + ); } } diff --git a/lib/features/instruments/details/widgets/instrument_details_summary.dart b/lib/features/instruments/details/widgets/instrument_details_summary.dart index 1b8676b..596ca6a 100644 --- a/lib/features/instruments/details/widgets/instrument_details_summary.dart +++ b/lib/features/instruments/details/widgets/instrument_details_summary.dart @@ -15,6 +15,7 @@ class InstrumentDetailsSummary extends StatelessWidget { builder: (context, constraints) { final padding = (constraints.maxWidth - ScreenSize.md.value) .clamp(16.0, ScreenSize.md.value); + return SingleChildScrollView( padding: EdgeInsets.symmetric(vertical: 16, horizontal: padding), physics: const NeverScrollableScrollPhysics(), diff --git a/lib/features/instruments/details/widgets/instrument_header_images.dart b/lib/features/instruments/details/widgets/instrument_header_images.dart index 69addc9..849c0c4 100644 --- a/lib/features/instruments/details/widgets/instrument_header_images.dart +++ b/lib/features/instruments/details/widgets/instrument_header_images.dart @@ -24,6 +24,7 @@ class InstrumentHeaderImages extends StatelessWidget { final imageQuantity = context.screenSize.isSmall ? 2 : 3; final largeImageHeight = imageHeight * imageQuantity + (imageQuantity - 1) * 16; + return Padding( padding: const EdgeInsets.all(24), child: Column( diff --git a/lib/features/instruments/instrument.dart b/lib/features/instruments/instrument.dart index 69c720f..3cbecff 100644 --- a/lib/features/instruments/instrument.dart +++ b/lib/features/instruments/instrument.dart @@ -6,17 +6,6 @@ part 'instrument.mapper.dart'; @MappableClass() class Instrument with InstrumentMappable { - Instrument({ - required this.id, - required this.name, - required this.description, - required this.imageUrl, - required this.gallery, - required this.type, - required this.translatedName, - required this.translatedDescription, - }); - final int id; final String name; final String type; @@ -28,4 +17,15 @@ class Instrument with InstrumentMappable { static const fromMap = InstrumentMapper.fromMap; static const fromJson = InstrumentMapper.fromJson; + + Instrument({ + required this.id, + required this.name, + required this.description, + required this.imageUrl, + required this.gallery, + required this.type, + required this.translatedName, + required this.translatedDescription, + }); } diff --git a/lib/features/instruments/instruments_repo.dart b/lib/features/instruments/instruments_repo.dart index 2d61c42..c38a647 100644 --- a/lib/features/instruments/instruments_repo.dart +++ b/lib/features/instruments/instruments_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/providers/client_network_provider.dart'; +import '../../core/providers/client_network.dart'; import '../../utils/immutable_list.dart'; import 'details/instrument_details_page.dart'; import 'instrument.dart'; @@ -19,20 +19,21 @@ abstract class InstrumentsRepo { } class InstrumentsRepoImpls implements InstrumentsRepo { - InstrumentsRepoImpls(this.ref); - final InstrumentsRepoRef ref; + InstrumentsRepoImpls(this.ref); + @override Future> getInstruments() async { try { - final response = await ref - .watch(clientNetworkProvider) - .value! - .get>(Endpoint.instruments.path); - final data = response.data!.cast>(); + final dio = await ref.watch(clientNetworkProvider.future); + final response = + await dio.get>(Endpoint.instruments.path); + final data = response.data ?? []; + return ImmutableList([ - for (final item in data) Instrument.fromMap(item), + for (final item in data.cast>()) + Instrument.fromMap(item), ]); } catch (e) { throw AppNetworkError.fromNetworkClientException(e); @@ -42,13 +43,12 @@ class InstrumentsRepoImpls implements InstrumentsRepo { @override Future getDetails(InstrumentId id) async { try { - final response = await ref - .watch(clientNetworkProvider) - .value! - .get>( - '${Endpoint.instruments.pathId}/$id', - ); - return Instrument.fromMap(response.data!); + final dio = await ref.watch(clientNetworkProvider.future); + final response = await dio.get>( + '${Endpoint.instruments.pathId}/$id', + ); + + return Instrument.fromMap(response.data ?? {}); } catch (e) { throw AppNetworkError.fromNetworkClientException(e); } diff --git a/lib/features/instruments/instruments_tab_providers.dart b/lib/features/instruments/instruments_tab_controller.dart similarity index 82% rename from lib/features/instruments/instruments_tab_providers.dart rename to lib/features/instruments/instruments_tab_controller.dart index e9d889c..396adbf 100644 --- a/lib/features/instruments/instruments_tab_providers.dart +++ b/lib/features/instruments/instruments_tab_controller.dart @@ -4,10 +4,10 @@ import '../../utils/immutable_list.dart'; import 'instrument.dart'; import 'instruments_repo.dart'; -part 'instruments_tab_providers.g.dart'; +part 'instruments_tab_controller.g.dart'; @riverpod -class InstrumentsTab extends _$InstrumentsTab { +class InstrumentsTabController extends _$InstrumentsTabController { @override FutureOr> build() async { return await ref.watch(instrumentsRepoProvider).getInstruments(); diff --git a/lib/features/instruments/instruments_tab_controller.g.dart b/lib/features/instruments/instruments_tab_controller.g.dart new file mode 100644 index 0000000..7f099a4 --- /dev/null +++ b/lib/features/instruments/instruments_tab_controller.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'instruments_tab_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$instrumentsTabControllerHash() => + r'af042f105ac11e3ed447c7fb429bd9abe1a3aaf1'; + +/// See also [InstrumentsTabController]. +@ProviderFor(InstrumentsTabController) +final instrumentsTabControllerProvider = AutoDisposeAsyncNotifierProvider< + InstrumentsTabController, ImmutableList>.internal( + InstrumentsTabController.new, + name: r'instrumentsTabControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$instrumentsTabControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$InstrumentsTabController + = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/instruments/instruments_tab_page.dart b/lib/features/instruments/instruments_tab_page.dart index a9b25b1..9a498d0 100644 --- a/lib/features/instruments/instruments_tab_page.dart +++ b/lib/features/instruments/instruments_tab_page.dart @@ -10,7 +10,7 @@ import '../../common_widgets/app_web_padding.dart'; import '../../core/extensions/app_localization_extension.dart'; import '../../utils/screen_size.dart'; import '../home/home_page_controller.dart'; -import 'instruments_tab_providers.dart'; +import 'instruments_tab_controller.dart'; import 'widgets/instrument_list_tile.dart'; class InstrumentsTabPage extends ConsumerWidget { @@ -35,8 +35,9 @@ class InstrumentsTabPage extends ConsumerWidget { SliverAnimatedSwitcher( duration: const Duration(milliseconds: 300), child: AppAsyncSliverWidget( - asyncValue: ref.watch(instrumentsTabProvider), - onErrorRetry: () => ref.invalidate(instrumentsTabProvider), + asyncValue: ref.watch(instrumentsTabControllerProvider), + onErrorRetry: () => + ref.invalidate(instrumentsTabControllerProvider), child: (value) => WebPaddingSliver.only( right: true, sliver: SliverSafeArea( @@ -46,6 +47,7 @@ class InstrumentsTabPage extends ConsumerWidget { itemCount: value.length, itemBuilder: (context, index) { final instrument = value[index]; + return InstrumentListTile( title: instrument.translatedName, originalTitle: instrument.name, diff --git a/lib/features/instruments/widgets/instrument_list_tile.dart b/lib/features/instruments/widgets/instrument_list_tile.dart index 586cba0..e369ce3 100644 --- a/lib/features/instruments/widgets/instrument_list_tile.dart +++ b/lib/features/instruments/widgets/instrument_list_tile.dart @@ -76,34 +76,26 @@ class _InstrumentListTileState extends State { children: [ TextSpan( text: widget.title, - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith( - fontWeight: FontWeight.bold, - color: context.colorScheme.onSurfaceVariant, - ), + style: context.headlineSmall.copyWith( + fontWeight: FontWeight.bold, + color: context.colorScheme.onSurfaceVariant, + ), ), if (widget.title != widget.originalTitle) TextSpan( text: ' ${widget.originalTitle}', - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith( - color: - context.colorScheme.onSurfaceVariant, - ), + style: context.titleLarge.copyWith( + color: context.colorScheme.onSurfaceVariant, + ), ), ], style: Theme.of(context).textTheme.headlineSmall, ), maxLines: 1, overflow: TextOverflow.ellipsis, - style: - Theme.of(context).textTheme.headlineSmall!.copyWith( - fontWeight: FontWeight.bold, - ), + style: context.headlineSmall.copyWith( + fontWeight: FontWeight.bold, + ), ), ), Row( diff --git a/lib/features/parades/parade.dart b/lib/features/parades/parade.dart index 745ac18..19201bf 100644 --- a/lib/features/parades/parade.dart +++ b/lib/features/parades/parade.dart @@ -9,38 +9,6 @@ typedef ParadeId = int; @MappableClass() class Parade with ParadeMappable { - Parade({ - required this.id, - required this.schoolId, - required this.carnivalId, - required this.carnivalName, - required this.enredo, - required this.carnavalescos, - required this.division, - required this.divisionNumber, - required this.subdivisionNumber, - required this.paradeYear, - required this.date, - required this.championParade, - required this.components, - required this.numberOfWings, - required this.numberOfFloats, - required this.numberOfTripods, - required this.placing, - required this.relegated, - required this.promoted, - required this.champion, - required this.performanceOrder, - required this.performanceDay, - required this.points, - required this.details, - required this.translatedCarnivalName, - required this.translatedEnredo, - required this.translatedDivision, - required this.translatedCarnavalescos, - required this.school, - }); - final ParadeId id; final SchoolId schoolId; final int carnivalId; @@ -73,10 +41,50 @@ class Parade with ParadeMappable { static const fromMap = ParadeMapper.fromMap; static const fromJson = ParadeMapper.fromJson; + + Parade({ + required this.id, + required this.schoolId, + required this.carnivalId, + required this.carnivalName, + required this.enredo, + required this.carnavalescos, + required this.division, + required this.divisionNumber, + required this.subdivisionNumber, + required this.paradeYear, + required this.date, + required this.championParade, + required this.components, + required this.numberOfWings, + required this.numberOfFloats, + required this.numberOfTripods, + required this.placing, + required this.relegated, + required this.promoted, + required this.champion, + required this.performanceOrder, + required this.performanceDay, + required this.points, + required this.details, + required this.translatedCarnivalName, + required this.translatedEnredo, + required this.translatedDivision, + required this.translatedCarnavalescos, + required this.school, + }); } @MappableClass() class ParadeQueryParams with ParadeQueryParamsMappable { + final String? language; + final String? filter; + final String? sort; + final String? sortOrder; + final String? search; + final int? page; + final int? pageSize; + ParadeQueryParams({ this.language, this.filter, @@ -86,12 +94,4 @@ class ParadeQueryParams with ParadeQueryParamsMappable { this.page, this.pageSize, }); - - final String? language; - final String? filter; - final String? sort; - final String? sortOrder; - final String? search; - final int? page; - final int? pageSize; } diff --git a/lib/features/parades/parade_extension.dart b/lib/features/parades/parade_extension.dart index 91572e0..325fee1 100644 --- a/lib/features/parades/parade_extension.dart +++ b/lib/features/parades/parade_extension.dart @@ -4,16 +4,23 @@ import '../../core/extensions/theme_of_context_extension.dart'; import '../schools/school.dart'; import 'parade.dart'; +const int _firstPlacing = 1; +const int _secondPlacing = 2; +const int _thirdPlacing = 3; +const int _fourthPlacing = 4; +const int _fifthPlacing = 5; +const int _sixthPlacing = 6; + extension ParadeExtension on Parade { String get getPerformanceIcon => divisionNumber == SchoolDivision.especial ? _firstDivisionIcon : _otherDivisionIcon; String get _firstDivisionIcon => switch ((placing, relegated, champion)) { - (1, _, _) => '🏆', - (2, _, _) => '🥈', - (3, _, _) => '🥉', - (4 || 5 || 6, _, false) => '🏅', + (_firstPlacing, _, _) => '🏆', + (_secondPlacing, _, _) => '🥈', + (_thirdPlacing, _, _) => '🥉', + (_fourthPlacing || _fifthPlacing || _sixthPlacing, _, false) => '🏅', (_, true, _) => '🔻', _ => '🎗️', }; @@ -21,8 +28,8 @@ extension ParadeExtension on Parade { String get _otherDivisionIcon => switch ((placing, relegated, champion)) { (1, _, true) => '🏆', (1, _, false) => '🥇', - (2, _, _) => '🥈', - (3, _, _) => '🥉', + (_secondPlacing, _, _) => '🥈', + (_thirdPlacing, _, _) => '🥉', (_, true, _) => '🔻', _ => '🎗️', }; @@ -40,13 +47,19 @@ extension ParadeExtension on Parade { promoted: promoted, champion: champion )) { - (placing: 1, relegated: _, promoted: _, champion: true) => - context.customColors.goldColor!, - (placing: 1, relegated: _, promoted: true, champion: false) => - context.customColors.silverColor!, - (placing: 2, relegated: _, promoted: true, champion: _) => - context.customColors.silverColor!, - (placing: 2 || 3 || 4 || 5, relegated: _, promoted: true, champion: _) => + (placing: _firstPlacing, relegated: _, promoted: _, champion: true) => + context.customGoldColor, + (placing: _firstPlacing, relegated: _, promoted: true, champion: false) => + context.customSilverColor, + (placing: _secondPlacing, relegated: _, promoted: true, champion: _) => + context.customSilverColor, + ( + placing: + _secondPlacing || _thirdPlacing || _fourthPlacing || _fifthPlacing, + relegated: _, + promoted: true, + champion: _ + ) => context.colorScheme.secondary, (placing: _, relegated: true, promoted: _, champion: _) => context.colorScheme.errorContainer, @@ -56,9 +69,13 @@ extension ParadeExtension on Parade { Color _specialMedalColor(BuildContext context) { return switch ((placing: placing, relegated: relegated)) { - (placing: 1, relegated: _) => context.customColors.goldColor!, - (placing: 2, relegated: _) => context.customColors.silverColor!, - (placing: 3 || 4 || 5 || 6, relegated: _) => + (placing: _firstPlacing, relegated: _) => context.customGoldColor, + (placing: _secondPlacing, relegated: _) => context.customSilverColor, + ( + placing: + _thirdPlacing || _fourthPlacing || _fifthPlacing || _sixthPlacing, + relegated: _ + ) => context.colorScheme.tertiaryContainer, (placing: _, relegated: true) => context.colorScheme.errorContainer, _ => context.colorScheme.primaryContainer, diff --git a/lib/features/parades/parades_repo.dart b/lib/features/parades/parades_repo.dart index d070fe3..ede60e0 100644 --- a/lib/features/parades/parades_repo.dart +++ b/lib/features/parades/parades_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/providers/client_network_provider.dart'; +import '../../core/providers/client_network.dart'; import '../../utils/immutable_list.dart'; import 'parade.dart'; @@ -20,26 +20,29 @@ abstract class ParadesRepo { } class ParadesRepoImpl implements ParadesRepo { - ParadesRepoImpl(this.ref); - final ParadesRepoRef ref; + ParadesRepoImpl(this.ref); @override Future> getParades({ ParadeQueryParams? queryParams, }) async { try { - final response = - await ref.watch(clientNetworkProvider).value!.get>( + final dio = await ref.watch(clientNetworkProvider.future); + final page = queryParams?.page; + final pageSize = queryParams?.pageSize; + final response = await dio.get>( Endpoint.parades.path, queryParameters: { - if (queryParams?.page != null) 'page': queryParams!.page, - if (queryParams?.pageSize != null) 'pageSize': queryParams!.pageSize, + if (page != null) 'page': page, + if (pageSize != null) 'pageSize': pageSize, }, ); - final data = response.data!.cast>(); + final data = response.data ?? >[]; + return ImmutableList([ - for (final item in data) Parade.fromMap(item), + for (final item in data.cast>()) + Parade.fromMap(item), ]); } catch (e) { throw AppNetworkError.fromNetworkClientException(e); @@ -49,11 +52,11 @@ class ParadesRepoImpl implements ParadesRepo { @override Future getParade(int id, {ParadeQueryParams? queryParams}) async { try { - final response = await ref - .watch(clientNetworkProvider) - .value! - .get>('${Endpoint.parades.pathId}/$id'); - return Parade.fromMap(response.data!); + final dio = await ref.watch(clientNetworkProvider.future); + final response = + await dio.get>('${Endpoint.parades.pathId}/$id'); + + return Parade.fromMap(response.data ?? {}); } catch (e) { throw AppNetworkError.fromNetworkClientException(e); } diff --git a/lib/features/parades/parades_tab_providers.dart b/lib/features/parades/parades_tab_controller.dart similarity index 85% rename from lib/features/parades/parades_tab_providers.dart rename to lib/features/parades/parades_tab_controller.dart index 2999ccf..918fa6f 100644 --- a/lib/features/parades/parades_tab_providers.dart +++ b/lib/features/parades/parades_tab_controller.dart @@ -5,10 +5,10 @@ import '../../utils/immutable_list.dart'; import 'parade.dart'; import 'parades_repo.dart'; -part 'parades_tab_providers.g.dart'; +part 'parades_tab_controller.g.dart'; @riverpod -class Parades extends _$Parades { +class ParadesTabController extends _$ParadesTabController { static const _pageSize = 10; @override @@ -19,19 +19,23 @@ class Parades extends _$Parades { } Future fetchNextPage({int pageSize = _pageSize}) async { + final current = state.value ?? const IList.empty(); try { final parades = await getParades( - page: state.value!.length ~/ pageSize + 1, + page: current.length ~/ pageSize + 1, pageSize: pageSize, ); if (parades.isNotEmpty) { - state = AsyncData(ImmutableList([...state.value!, ...parades])); + state = AsyncData(ImmutableList([...current, ...parades])); + return true; } ref.read(paradesTabReachedLimitProvider.notifier).setReachedLimit(); + return false; } catch (e, st) { logViews.warning('Failed to fetch next page $e', e, st); + return null; } } @@ -48,8 +52,10 @@ class Parades extends _$Parades { ); if (parades.isEmpty || parades.length < pageSize) { ref.read(paradesTabReachedLimitProvider.notifier).setReachedLimit(); + return parades; } + return parades; } } diff --git a/lib/features/parades/parades_tab_providers.g.dart b/lib/features/parades/parades_tab_controller.g.dart similarity index 74% rename from lib/features/parades/parades_tab_providers.g.dart rename to lib/features/parades/parades_tab_controller.g.dart index bf118ed..81c463e 100644 --- a/lib/features/parades/parades_tab_providers.g.dart +++ b/lib/features/parades/parades_tab_controller.g.dart @@ -1,26 +1,29 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'parades_tab_providers.dart'; +part of 'parades_tab_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$paradesHash() => r'212ae8bb76bd4971f4aa3e384283957fe8dd780c'; +String _$paradesTabControllerHash() => + r'6c5d4cd7a66b471fdd03be710654b7eea981fc4e'; -/// See also [Parades]. -@ProviderFor(Parades) -final paradesProvider = - AutoDisposeAsyncNotifierProvider>.internal( - Parades.new, - name: r'paradesProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$paradesHash, +/// See also [ParadesTabController]. +@ProviderFor(ParadesTabController) +final paradesTabControllerProvider = AutoDisposeAsyncNotifierProvider< + ParadesTabController, ImmutableList>.internal( + ParadesTabController.new, + name: r'paradesTabControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$paradesTabControllerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$Parades = AutoDisposeAsyncNotifier>; +typedef _$ParadesTabController + = AutoDisposeAsyncNotifier>; String _$paradesTabReachedLimitHash() => r'e24c9ae9f3318555dd548f146cde6bc7ff0fb670'; diff --git a/lib/features/parades/parades_tab_page.dart b/lib/features/parades/parades_tab_page.dart index 8660bcb..bbfcbba 100644 --- a/lib/features/parades/parades_tab_page.dart +++ b/lib/features/parades/parades_tab_page.dart @@ -12,7 +12,7 @@ import '../../utils/debouncer.dart'; import '../../utils/screen_size.dart'; import '../home/home_page_controller.dart'; import '../schools/school.dart'; -import 'parades_tab_providers.dart'; +import 'parades_tab_controller.dart'; import 'widgets/parade_item.dart'; import 'widgets/parade_item_year_line.dart'; @@ -29,30 +29,22 @@ class ParadesTabPage extends ConsumerStatefulWidget { class _ParadesTabPageState extends ConsumerState { final _debouncer = Debouncer(defaultDelay); final _listController = ListController(); - ScrollController? controller; + late final ScrollController controller = PrimaryScrollController.of(context); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - controller = PrimaryScrollController.of(context); - controller?.addListener(_loadMoreListener); + controller.addListener(_loadMoreListener); }); } - @override - void dispose() { - _debouncer.dispose(); - _listController.dispose(); - controller?.removeListener(_loadMoreListener); - super.dispose(); - } - void _loadMoreListener() { - final position = controller!.position; + final position = controller.position; if (position.pixels == position.maxScrollExtent) { if (!ref.read(paradesTabReachedLimitProvider)) { - _debouncer.run(ref.read(paradesProvider.notifier).fetchNextPage); + _debouncer + .run(ref.read(paradesTabControllerProvider.notifier).fetchNextPage); } } } @@ -71,10 +63,10 @@ class _ParadesTabPageState extends ConsumerState { ), ), AppAsyncSliverWidget( - asyncValue: ref.watch(paradesProvider), + asyncValue: ref.watch(paradesTabControllerProvider), onErrorRetry: () async => Future.delayed(const Duration(milliseconds: 500), () { - ref.invalidate(paradesProvider); + ref.invalidate(paradesTabControllerProvider); }), child: (value) => SliverCrossAxisConstrained( maxCrossAxisExtent: ScreenSize.md.value, @@ -83,6 +75,7 @@ class _ParadesTabPageState extends ConsumerState { listController: _listController, itemBuilder: (context, index) { final parade = value[index]; + return ProviderScope( overrides: [ currentParadeProvider.overrideWithValue(value[index]), @@ -125,15 +118,11 @@ class _ParadesTabPageState extends ConsumerState { ); } - void animateToItem(int index) { - _listController.animateToItem( - index: index, - scrollController: controller!, - alignment: 0.5, - // You can provide duration and curve depending on the estimated - // distance between currentPosition and the target item position. - duration: (estimatedDistance) => const Duration(milliseconds: 250), - curve: (estimatedDistance) => Curves.easeInOut, - ); + @override + void dispose() { + _debouncer.dispose(); + _listController.dispose(); + controller.removeListener(_loadMoreListener); + super.dispose(); } } diff --git a/lib/features/parades/widgets/parade_item.dart b/lib/features/parades/widgets/parade_item.dart index 96ea923..8c284d3 100644 --- a/lib/features/parades/widgets/parade_item.dart +++ b/lib/features/parades/widgets/parade_item.dart @@ -10,7 +10,7 @@ import '../../../core/extensions/theme_of_context_extension.dart'; import '../../schools/school_extensions.dart'; import '../parade.dart'; import '../parade_extension.dart'; -import '../parades_tab_providers.dart'; +import '../parades_tab_controller.dart'; import 'parade_item_bottom_row.dart'; import 'parade_item_sidebar.dart'; @@ -23,6 +23,7 @@ class ParadeItem extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final parade = ref.watch(currentParadeProvider); final medalColor = parade.medalColor(context); + return Card( clipBehavior: Clip.antiAlias, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), @@ -71,6 +72,7 @@ class ParadeItemContent extends StatelessWidget { @override Widget build(BuildContext context) { final medalColor = parade.medalColor(context); + return Stack( children: [ Column( @@ -131,6 +133,7 @@ class ParadeItemBadge extends StatelessWidget { Widget build(BuildContext context) { final medalColor = parade.medalColor(context); final divisionName = parade.divisionNumber.shortName(context); + return DecoratedBox( decoration: BoxDecoration( borderRadius: const BorderRadius.only( @@ -155,7 +158,7 @@ class ParadeItemBadge extends StatelessWidget { padding: const EdgeInsets.all(8), child: Text( divisionName, - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( color: context.colorScheme.onSurface, fontWeight: FontWeight.bold, ), @@ -181,6 +184,7 @@ class ParadeItemTextContentHeader extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final showOriginal = ref.watch(paradeShowOriginalProvider); final enredo = showOriginal ? parade.enredo : parade.translatedEnredo; + return Padding( padding: padding, child: Column( @@ -197,7 +201,7 @@ class ParadeItemTextContentHeader extends ConsumerWidget { ? '${parade.school.name}\n' : '${parade.school.translatedName}\n', maxLines: 2, - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( color: context.colorScheme.onSurfaceVariant, ), strutStyle: const StrutStyle( @@ -210,7 +214,7 @@ class ParadeItemTextContentHeader extends ConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( parade.divisionNumber.shortName(context), - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( color: Colors.transparent, ), ), @@ -227,7 +231,7 @@ class ParadeItemTextContentHeader extends ConsumerWidget { TextSpan(text: parade.details), ], ), - style: context.textTheme.titleSmall!.copyWith( + style: context.titleSmall.copyWith( fontWeight: FontWeight.w700, ), strutStyle: const StrutStyle( @@ -254,6 +258,7 @@ class ParadeItemTextContentDetails extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showOriginal = ref.watch(paradeShowOriginalProvider); + return Padding( padding: padding, child: Column( @@ -289,13 +294,13 @@ class ParadeItemTextContentDetails extends ConsumerWidget { children: [ TextSpan( text: '${context.loc.schoolComponents}: ', - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( fontWeight: FontWeight.w400, ), ), TextSpan( text: parade.components.toString(), - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( fontWeight: FontWeight.w400, ), ), @@ -333,7 +338,7 @@ class ParadeItemTextContentDetails extends ConsumerWidget { : '', ), ], - style: context.textTheme.labelSmall!.copyWith( + style: context.labelSmall.copyWith( fontWeight: FontWeight.w300, fontStyle: FontStyle.italic, ), diff --git a/lib/features/parades/widgets/parade_item_bottom_row.dart b/lib/features/parades/widgets/parade_item_bottom_row.dart index 25ab49c..63a2e62 100644 --- a/lib/features/parades/widgets/parade_item_bottom_row.dart +++ b/lib/features/parades/widgets/parade_item_bottom_row.dart @@ -20,6 +20,7 @@ class ParadeItemBottomRow extends StatelessWidget { final medalColor = parade.medalColor(context); final day = parade.performanceDay.intlOrdinal(context); final order = parade.performanceOrder.intlOrdinal(context); + return AppAnimatedLinearGradient( duration: const Duration(seconds: 10), colors: [ @@ -54,7 +55,7 @@ class ParadeItemBottomRow extends StatelessWidget { '$order${context.loc.schoolToParade}', maxLines: 1, overflow: TextOverflow.ellipsis, - style: context.textTheme.labelMedium!.copyWith( + style: context.labelMedium.copyWith( fontStyle: FontStyle.italic, ), ), diff --git a/lib/features/parades/widgets/parade_item_sidebar.dart b/lib/features/parades/widgets/parade_item_sidebar.dart index d2d54b9..f908b6b 100644 --- a/lib/features/parades/widgets/parade_item_sidebar.dart +++ b/lib/features/parades/widgets/parade_item_sidebar.dart @@ -19,6 +19,7 @@ class ParadeItemSideBar extends StatelessWidget { @override Widget build(BuildContext context) { final medalColor = parade.medalColor(context); + return Container( width: 48, height: double.infinity, @@ -64,7 +65,7 @@ class ParadeItemSideBar extends StatelessWidget { TextSpan( children: [ TextSpan( - style: context.textTheme.headlineSmall!.copyWith( + style: context.headlineSmall.copyWith( color: context.colorScheme.onSurface, fontWeight: FontWeight.w600, ), diff --git a/lib/features/schools/details/school_details_page.dart b/lib/features/schools/details/school_details_page.dart index 3a5a99a..b06b8f9 100644 --- a/lib/features/schools/details/school_details_page.dart +++ b/lib/features/schools/details/school_details_page.dart @@ -6,12 +6,12 @@ import '../../../common_widgets/app_cupertino_button.dart'; import '../../../common_widgets/app_page_indicator.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/intl_extension.dart'; -import '../../../core/extensions/string_extension.dart'; +import '../../../core/extensions/string_extensions.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; import '../school.dart'; import '../school_extensions.dart'; import '../widgets/school_flag.dart'; -import 'schools_details_providers.dart'; +import 'schools_details_controller.dart'; class SchoolDetailsPage extends ConsumerStatefulWidget { const SchoolDetailsPage({ @@ -34,7 +34,8 @@ class _SchoolDetailsPageState extends ConsumerState { @override Widget build(BuildContext context) { - final school = ref.watch(selectedSchoolProvider(widget.id)); + final school = ref.watch(schoolsDetailsControllerProvider(widget.id)); + return ListView( shrinkWrap: true, children: [ @@ -155,6 +156,9 @@ class SchoolDetailsText extends StatefulWidget { class _SchoolDetailsTextState extends State { @override Widget build(BuildContext context) { + final school = widget.school; + final foundationDate = school.foundationDate; + return AnimatedSwitcher( duration: kThemeAnimationDuration, child: Padding( @@ -176,7 +180,7 @@ class _SchoolDetailsTextState extends State { : '${widget.school.name}${'\n'}', maxLines: 2, overflow: TextOverflow.ellipsis, - style: context.textTheme.headlineMedium! + style: context.headlineMedium .copyWith(fontWeight: FontWeight.w600), ), ), @@ -190,7 +194,7 @@ class _SchoolDetailsTextState extends State { children: [ Text( '${widget.school.firstDivisionChampionships}', - style: context.textTheme.headlineMedium!.copyWith( + style: context.headlineMedium.copyWith( color: context.customColors.goldColor, fontWeight: FontWeight.bold, height: 1, @@ -235,11 +239,11 @@ class _SchoolDetailsTextState extends State { ? widget.school.symbols.join(', ') : widget.school.translatedSymbols.join(', '), ), - if (widget.school.foundationDate != null) + if (foundationDate != null) SchoolTextTile( icon: Icons.date_range_outlined, title: '${context.loc.schoolFoundation}: ', - content: widget.school.foundationDate!.intlShort(context), + content: foundationDate.intlShort(context), ), if (widget.school.godmotherSchool.isNotEmpty) SchoolTextTile( @@ -352,10 +356,10 @@ class SchoolTextTile extends StatelessWidget { TextSpan(text: title), TextSpan( text: content, - style: context.textTheme.bodyLarge, + style: context.bodyLarge, ), ], - style: context.textTheme.bodyLarge!.copyWith( + style: context.bodyLarge.copyWith( fontWeight: FontWeight.w600, ), ), diff --git a/lib/features/schools/details/schools_details_controller.dart b/lib/features/schools/details/schools_details_controller.dart new file mode 100644 index 0000000..525b5b5 --- /dev/null +++ b/lib/features/schools/details/schools_details_controller.dart @@ -0,0 +1,15 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../school.dart'; +import '../schools_tab_controller.dart'; + +part 'schools_details_controller.g.dart'; + +@riverpod +class SchoolsDetailsController extends _$SchoolsDetailsController { + @override + School build(int id) { + final school = ref.watch(schoolsTabControllerProvider).requireValue; + + return school.firstWhere((school) => school.id == id); + } +} diff --git a/lib/features/schools/details/schools_details_providers.g.dart b/lib/features/schools/details/schools_details_controller.g.dart similarity index 53% rename from lib/features/schools/details/schools_details_providers.g.dart rename to lib/features/schools/details/schools_details_controller.g.dart index e3080b6..8a7d63d 100644 --- a/lib/features/schools/details/schools_details_providers.g.dart +++ b/lib/features/schools/details/schools_details_controller.g.dart @@ -1,12 +1,13 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'schools_details_providers.dart'; +part of 'schools_details_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$selectedSchoolHash() => r'caae5ffd4b733525c963b09b2b47726e3a1920ec'; +String _$schoolsDetailsControllerHash() => + r'29336ca2472c1525ae7bf07f6539f5557d5e486b'; /// Copied from Dart SDK class _SystemHash { @@ -29,7 +30,8 @@ class _SystemHash { } } -abstract class _$SelectedSchool extends BuildlessAutoDisposeNotifier { +abstract class _$SchoolsDetailsController + extends BuildlessAutoDisposeNotifier { late final int id; School build( @@ -37,14 +39,14 @@ abstract class _$SelectedSchool extends BuildlessAutoDisposeNotifier { ); } -/// See also [SelectedSchool]. -@ProviderFor(SelectedSchool) -const selectedSchoolProvider = SelectedSchoolFamily(); +/// See also [SchoolsDetailsController]. +@ProviderFor(SchoolsDetailsController) +const schoolsDetailsControllerProvider = SchoolsDetailsControllerFamily(); -/// See also [SelectedSchool]. -class SelectedSchoolFamily extends Family { - /// See also [SelectedSchool]. - const SelectedSchoolFamily(); +/// See also [SchoolsDetailsController]. +class SchoolsDetailsControllerFamily extends Family { + /// See also [SchoolsDetailsController]. + const SchoolsDetailsControllerFamily(); static const Iterable? _dependencies = null; @@ -58,21 +60,21 @@ class SelectedSchoolFamily extends Family { _allTransitiveDependencies; @override - String? get name => r'selectedSchoolProvider'; + String? get name => r'schoolsDetailsControllerProvider'; - /// See also [SelectedSchool]. - SelectedSchoolProvider call( + /// See also [SchoolsDetailsController]. + SchoolsDetailsControllerProvider call( int id, ) { - return SelectedSchoolProvider( + return SchoolsDetailsControllerProvider( id, ); } @visibleForOverriding @override - SelectedSchoolProvider getProviderOverride( - covariant SelectedSchoolProvider provider, + SchoolsDetailsControllerProvider getProviderOverride( + covariant SchoolsDetailsControllerProvider provider, ) { return call( provider.id, @@ -80,48 +82,48 @@ class SelectedSchoolFamily extends Family { } /// Enables overriding the behavior of this provider, no matter the parameters. - Override overrideWith(SelectedSchool Function() create) { - return _$SelectedSchoolFamilyOverride(this, create); + Override overrideWith(SchoolsDetailsController Function() create) { + return _$SchoolsDetailsControllerFamilyOverride(this, create); } } -class _$SelectedSchoolFamilyOverride implements FamilyOverride { - _$SelectedSchoolFamilyOverride(this.overriddenFamily, this.create); +class _$SchoolsDetailsControllerFamilyOverride implements FamilyOverride { + _$SchoolsDetailsControllerFamilyOverride(this.overriddenFamily, this.create); - final SelectedSchool Function() create; + final SchoolsDetailsController Function() create; @override - final SelectedSchoolFamily overriddenFamily; + final SchoolsDetailsControllerFamily overriddenFamily; @override - SelectedSchoolProvider getProviderOverride( - covariant SelectedSchoolProvider provider, + SchoolsDetailsControllerProvider getProviderOverride( + covariant SchoolsDetailsControllerProvider provider, ) { return provider._copyWith(create); } } -/// See also [SelectedSchool]. -class SelectedSchoolProvider - extends AutoDisposeNotifierProviderImpl { - /// See also [SelectedSchool]. - SelectedSchoolProvider( +/// See also [SchoolsDetailsController]. +class SchoolsDetailsControllerProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [SchoolsDetailsController]. + SchoolsDetailsControllerProvider( int id, ) : this._internal( - () => SelectedSchool()..id = id, - from: selectedSchoolProvider, - name: r'selectedSchoolProvider', + () => SchoolsDetailsController()..id = id, + from: schoolsDetailsControllerProvider, + name: r'schoolsDetailsControllerProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$selectedSchoolHash, - dependencies: SelectedSchoolFamily._dependencies, + : _$schoolsDetailsControllerHash, + dependencies: SchoolsDetailsControllerFamily._dependencies, allTransitiveDependencies: - SelectedSchoolFamily._allTransitiveDependencies, + SchoolsDetailsControllerFamily._allTransitiveDependencies, id: id, ); - SelectedSchoolProvider._internal( + SchoolsDetailsControllerProvider._internal( super.create, { required super.name, required super.dependencies, @@ -135,7 +137,7 @@ class SelectedSchoolProvider @override School runNotifierBuild( - covariant SelectedSchool notifier, + covariant SchoolsDetailsController notifier, ) { return notifier.build( id, @@ -143,10 +145,10 @@ class SelectedSchoolProvider } @override - Override overrideWith(SelectedSchool Function() create) { + Override overrideWith(SchoolsDetailsController Function() create) { return ProviderOverride( origin: this, - override: SelectedSchoolProvider._internal( + override: SchoolsDetailsControllerProvider._internal( () => create()..id = id, from: from, name: null, @@ -164,14 +166,15 @@ class SelectedSchoolProvider } @override - AutoDisposeNotifierProviderElement createElement() { - return _SelectedSchoolProviderElement(this); + AutoDisposeNotifierProviderElement + createElement() { + return _SchoolsDetailsControllerProviderElement(this); } - SelectedSchoolProvider _copyWith( - SelectedSchool Function() create, + SchoolsDetailsControllerProvider _copyWith( + SchoolsDetailsController Function() create, ) { - return SelectedSchoolProvider._internal( + return SchoolsDetailsControllerProvider._internal( () => create()..id = id, name: name, dependencies: dependencies, @@ -184,7 +187,7 @@ class SelectedSchoolProvider @override bool operator ==(Object other) { - return other is SelectedSchoolProvider && other.id == id; + return other is SchoolsDetailsControllerProvider && other.id == id; } @override @@ -196,18 +199,18 @@ class SelectedSchoolProvider } } -mixin SelectedSchoolRef on AutoDisposeNotifierProviderRef { +mixin SchoolsDetailsControllerRef on AutoDisposeNotifierProviderRef { /// The parameter `id` of this provider. int get id; } -class _SelectedSchoolProviderElement - extends AutoDisposeNotifierProviderElement - with SelectedSchoolRef { - _SelectedSchoolProviderElement(super.provider); +class _SchoolsDetailsControllerProviderElement + extends AutoDisposeNotifierProviderElement + with SchoolsDetailsControllerRef { + _SchoolsDetailsControllerProviderElement(super.provider); @override - int get id => (origin as SelectedSchoolProvider).id; + int get id => (origin as SchoolsDetailsControllerProvider).id; } // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/features/schools/details/schools_details_providers.dart b/lib/features/schools/details/schools_details_providers.dart deleted file mode 100644 index cdc4c9b..0000000 --- a/lib/features/schools/details/schools_details_providers.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../school.dart'; -import '../schools_tab_providers.dart'; - -part 'schools_details_providers.g.dart'; - -@riverpod -class SelectedSchool extends _$SelectedSchool { - @override - School build(int id) { - final school = ref.watch(schoolsProvider).value!.firstWhere( - (school) => school.id == id, - ); - return school; - } -} diff --git a/lib/features/schools/school.dart b/lib/features/schools/school.dart index 4faac3a..22b91cc 100644 --- a/lib/features/schools/school.dart +++ b/lib/features/schools/school.dart @@ -12,31 +12,6 @@ typedef SchoolId = int; @MappableClass() class School with SchoolMappable { - const School({ - required this.id, - required this.name, - required this.translatedName, - required this.imageUrl, - required this.foundationDate, - required this.godmotherSchool, - required this.colors, - required this.colorsCode, - required this.symbols, - required this.carnivalCategory, - required this.currentDivision, - required this.divisionNumber, - required this.subdivisionNumber, - required this.firstDivisionChampionships, - required this.country, - required this.leagueLocation, - required this.lastPosition, - required this.translatedColors, - required this.translatedSymbols, - required this.translatedGodmotherSchool, - required this.translatedLeagueLocation, - required this.translatedCountry, - }); - final SchoolId id; final String name; final String imageUrl; @@ -58,13 +33,38 @@ class School with SchoolMappable { final String translatedGodmotherSchool; final String translatedCountry; final String translatedLeagueLocation; - @MappableField(hook: ColorHook(), key: 'colors') + @MappableField(hook: SchoolColorHook(), key: 'colors') final ImmutableList colorsCode; @MappableField(key: 'divisionNumber') final SchoolDivision currentDivision; static const fromMap = SchoolMapper.fromMap; static const fromJson = SchoolMapper.fromJson; + + const School({ + required this.id, + required this.name, + required this.translatedName, + required this.imageUrl, + required this.foundationDate, + required this.godmotherSchool, + required this.colors, + required this.colorsCode, + required this.symbols, + required this.carnivalCategory, + required this.currentDivision, + required this.divisionNumber, + required this.subdivisionNumber, + required this.firstDivisionChampionships, + required this.country, + required this.leagueLocation, + required this.lastPosition, + required this.translatedColors, + required this.translatedSymbols, + required this.translatedGodmotherSchool, + required this.translatedLeagueLocation, + required this.translatedCountry, + }); } @MappableEnum(caseStyle: CaseStyle.upperCase) @@ -79,25 +79,37 @@ enum SchoolCategory { blocoDeRua, } +class _SchoolDivisionConstants { + static const int especial = 1; + static const int ouro = 2; + static const int prata = 3; + static const int bronze = 4; + static const int avaliacao = 5; + static const int mirins = 6; + static const int blocosDeEnredo1 = 7; + static const int blocosDeEnredo2 = 8; + static const int blocosDeRua = 9; +} + @MappableEnum() enum SchoolDivision { - @MappableValue(1) + @MappableValue(_SchoolDivisionConstants.especial) especial, - @MappableValue(2) + @MappableValue(_SchoolDivisionConstants.ouro) ouro, - @MappableValue(3) + @MappableValue(_SchoolDivisionConstants.prata) prata, - @MappableValue(4) + @MappableValue(_SchoolDivisionConstants.bronze) bronze, - @MappableValue(5) + @MappableValue(_SchoolDivisionConstants.avaliacao) avaliacao, - @MappableValue(6) + @MappableValue(_SchoolDivisionConstants.mirins) mirins, - @MappableValue(7) + @MappableValue(_SchoolDivisionConstants.blocosDeEnredo1) blocosDeEnredo1, - @MappableValue(8) + @MappableValue(_SchoolDivisionConstants.blocosDeEnredo2) blocosDeEnredo2, - @MappableValue(9) + @MappableValue(_SchoolDivisionConstants.blocosDeRua) blocosDeRua } @@ -122,8 +134,9 @@ class DateTimeHook extends MappingHook { //1946/6/24 if (value.isEmpty) return null; final data = value.trim().split('/'); + return DateTime( - int.parse(data[0]), + int.parse(data.first), data.length > 1 ? int.parse(data[1]) : 1, data.length > 2 ? int.parse(data[2]) : 1, ); diff --git a/lib/features/schools/school.mapper.dart b/lib/features/schools/school.mapper.dart index 033ad7a..9671753 100644 --- a/lib/features/schools/school.mapper.dart +++ b/lib/features/schools/school.mapper.dart @@ -79,23 +79,23 @@ class SchoolDivisionMapper extends EnumMapper { @override SchoolDivision decode(dynamic value) { switch (value) { - case 1: + case _SchoolDivisionConstants.especial: return SchoolDivision.especial; - case 2: + case _SchoolDivisionConstants.ouro: return SchoolDivision.ouro; - case 3: + case _SchoolDivisionConstants.prata: return SchoolDivision.prata; - case 4: + case _SchoolDivisionConstants.bronze: return SchoolDivision.bronze; - case 5: + case _SchoolDivisionConstants.avaliacao: return SchoolDivision.avaliacao; - case 6: + case _SchoolDivisionConstants.mirins: return SchoolDivision.mirins; - case 7: + case _SchoolDivisionConstants.blocosDeEnredo1: return SchoolDivision.blocosDeEnredo1; - case 8: + case _SchoolDivisionConstants.blocosDeEnredo2: return SchoolDivision.blocosDeEnredo2; - case 9: + case _SchoolDivisionConstants.blocosDeRua: return SchoolDivision.blocosDeRua; default: throw MapperException.unknownEnumValue(value); @@ -106,23 +106,23 @@ class SchoolDivisionMapper extends EnumMapper { dynamic encode(SchoolDivision self) { switch (self) { case SchoolDivision.especial: - return 1; + return _SchoolDivisionConstants.especial; case SchoolDivision.ouro: - return 2; + return _SchoolDivisionConstants.ouro; case SchoolDivision.prata: - return 3; + return _SchoolDivisionConstants.prata; case SchoolDivision.bronze: - return 4; + return _SchoolDivisionConstants.bronze; case SchoolDivision.avaliacao: - return 5; + return _SchoolDivisionConstants.avaliacao; case SchoolDivision.mirins: - return 6; + return _SchoolDivisionConstants.mirins; case SchoolDivision.blocosDeEnredo1: - return 7; + return _SchoolDivisionConstants.blocosDeEnredo1; case SchoolDivision.blocosDeEnredo2: - return 8; + return _SchoolDivisionConstants.blocosDeEnredo2; case SchoolDivision.blocosDeRua: - return 9; + return _SchoolDivisionConstants.blocosDeRua; } } } @@ -171,7 +171,7 @@ class SchoolMapper extends ClassMapperBase { Field('colors', _$colors); static IList _$colorsCode(School v) => v.colorsCode; static const Field> _f$colorsCode = - Field('colorsCode', _$colorsCode, key: 'colors', hook: ColorHook()); + Field('colorsCode', _$colorsCode, key: 'colors', hook: SchoolColorHook()); static IList _$symbols(School v) => v.symbols; static const Field> _f$symbols = Field('symbols', _$symbols); diff --git a/lib/features/schools/school_color_hook.dart b/lib/features/schools/school_color_hook.dart index 15e39c8..4f0e599 100644 --- a/lib/features/schools/school_color_hook.dart +++ b/lib/features/schools/school_color_hook.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; -class ColorHook extends MappingHook { - const ColorHook(); +class SchoolColorHook extends MappingHook { + const SchoolColorHook(); @override Object? beforeDecode(Object? value) { @@ -14,6 +14,7 @@ class ColorHook extends MappingHook { for (final color in value) _getColor(color as String), ]); } + return value; } @@ -56,6 +57,7 @@ class ColorHook extends MappingHook { Color _defaultColor(String color) { logColorParse.info('Color not parsed $color'); + return Colors.white; } } diff --git a/lib/features/schools/school_extensions.dart b/lib/features/schools/school_extensions.dart index 96b9baa..06b19f1 100644 --- a/lib/features/schools/school_extensions.dart +++ b/lib/features/schools/school_extensions.dart @@ -3,9 +3,30 @@ library; import 'package:flutter/material.dart'; import '../../core/extensions/app_localization_extension.dart'; -import '../../core/extensions/string_extension.dart'; +import '../../core/extensions/string_extensions.dart'; import 'school.dart'; +extension SchoolExtensions on School { + bool searchLogic(String search) { + if (name.removeAccents.contains(search)) { + return true; + } + if (symbols.join(' ').removeAccents.contains(search)) { + return true; + } + + if (godmotherSchool.removeAccents.contains(search)) { + return true; + } + + if (colorsCode.join(' ').removeAccents.contains(search)) { + return true; + } + + return false; + } +} + extension SchoolDivisionExtension on SchoolDivision { String fullName(BuildContext context) => switch (this) { (SchoolDivision.especial) => context.loc.schoolDivisionSpecialFullName, @@ -36,24 +57,3 @@ extension SchoolDivisionExtension on SchoolDivision { (SchoolDivision.blocosDeRua) => context.loc.schoolDivisionStreetBloco, }; } - -extension SearchLogicSchoolExtension on School { - bool searchLogic(String search) { - if (name.removeAccents.contains(search)) { - return true; - } - if (symbols.join(' ').removeAccents.contains(search)) { - return true; - } - - if (godmotherSchool.removeAccents.contains(search)) { - return true; - } - - if (colorsCode.join(' ').removeAccents.contains(search)) { - return true; - } - - return false; - } -} diff --git a/lib/features/schools/schools_repo.dart b/lib/features/schools/schools_repo.dart index cee4b83..9fe5506 100644 --- a/lib/features/schools/schools_repo.dart +++ b/lib/features/schools/schools_repo.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/providers/client_network_provider.dart'; +import '../../core/providers/client_network.dart'; import '../../utils/immutable_list.dart'; import 'school.dart'; @@ -21,10 +21,10 @@ abstract class SchoolsRepo { } class SchoolsRepoImpls implements SchoolsRepo { - SchoolsRepoImpls(this.ref); - final SchoolsRepoRef ref; + SchoolsRepoImpls(this.ref); + @override Future> getSchools({ required int page, @@ -33,7 +33,7 @@ class SchoolsRepoImpls implements SchoolsRepo { required String search, }) async { try { - final networkClient = ref.watch(clientNetworkProvider).requireValue; + final networkClient = await ref.watch(clientNetworkProvider.future); final response = await networkClient.get>( search.isEmpty ? Endpoint.schools.path : Endpoint.schools.pathSearch, queryParameters: { @@ -43,9 +43,11 @@ class SchoolsRepoImpls implements SchoolsRepo { // if (sort.isNotEmpty) 'sort': sort, }, ); - final data = response.data!.cast>(); + final data = response.data ?? >[]; + return ImmutableList([ - for (final item in data) School.fromMap(item), + for (final item in data.cast>()) + School.fromMap(item), ]); } catch (e) { throw AppNetworkError.fromNetworkClientException(e); diff --git a/lib/features/schools/schools_tab_providers.dart b/lib/features/schools/schools_tab_controller.dart similarity index 81% rename from lib/features/schools/schools_tab_providers.dart rename to lib/features/schools/schools_tab_controller.dart index 42e8747..e92e78f 100644 --- a/lib/features/schools/schools_tab_providers.dart +++ b/lib/features/schools/schools_tab_controller.dart @@ -1,37 +1,46 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../core/providers/prefs_provider.dart'; +import '../../core/providers/prefs.dart'; import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; import 'school.dart'; import 'schools_repo.dart'; import 'widgets/school_card.dart'; -part 'schools_tab_providers.g.dart'; +part 'schools_tab_controller.g.dart'; @riverpod -class Schools extends _$Schools { +class SchoolsTabController extends _$SchoolsTabController { static const _pageSize = 12; @override - FutureOr> build() async { + Future> build() async { return getSchools(pageSize: _pageSize); } Future fetchNextPage({int pageSize = _pageSize}) async { + final current = state.value ?? const ImmutableList.empty(); try { final schools = await getSchools( - page: state.value!.length ~/ pageSize + 1, + page: current.length ~/ pageSize + 1, pageSize: pageSize, ); if (schools.isNotEmpty) { - state = AsyncData(ImmutableList([...state.value!, ...schools])); + state = AsyncData( + ImmutableList([ + ...current, + ...schools, + ]), + ); + return true; } ref.read(schoolReachedMaxProvider.notifier).reached(); + return false; } catch (e, st) { logViews.warning('Failed to fetch next page $e', e, st); + return null; } } @@ -56,8 +65,10 @@ class Schools extends _$Schools { ); if (schools.isEmpty || schools.length < pageSize) { ref.read(schoolReachedMaxProvider.notifier).reached(); + return schools; } + return schools; } } @@ -66,24 +77,26 @@ class Schools extends _$Schools { class FavoriteSchools extends _$FavoriteSchools { @override ImmutableList build() { - final prefs = ref.watch(prefsProvider).value!; - return ImmutableList(prefs.getStringList('favoriteSchools') ?? []); + final prefs = ref.watch(prefsProvider).value; + + return ImmutableList(prefs?.getStringList('favoriteSchools') ?? []); } void toggleFavorite(SchoolId id) { - final prefs = ref.watch(prefsProvider).value!; - final favoriteSchools = prefs.getStringList('favoriteSchools') ?? []; + final prefs = ref.watch(prefsProvider).value; + final favoriteSchools = prefs?.getStringList('favoriteSchools') ?? []; if (favoriteSchools.contains('$id')) { favoriteSchools.remove('$id'); } else { favoriteSchools.add('$id'); } - prefs.setStringList('favoriteSchools', favoriteSchools); + prefs?.setStringList('favoriteSchools', favoriteSchools); state = ImmutableList(favoriteSchools); } bool isFavorite(SchoolId id) { final favoriteSchools = state; + return favoriteSchools.contains('$id'); } } @@ -92,8 +105,9 @@ class FavoriteSchools extends _$FavoriteSchools { class SchoolDivisions extends _$SchoolDivisions { @override Map build() { - final schools = ref.watch(schoolsProvider).valueOrNull; + final schools = ref.watch(schoolsTabControllerProvider).valueOrNull; final divisions = schools?.map((e) => e.currentDivision).toSet() ?? {}; + return { for (final division in divisions) division: true, }; @@ -122,14 +136,12 @@ class SchoolDivisions extends _$SchoolDivisions { @riverpod class SearchedSchool extends _$SearchedSchool { @override - String build() { - return ''; - } + String build() => ''; void setSearch(String search) { if (state != search.trim()) { state = search.trim(); - ref.read(schoolsProvider.notifier).searchSchools(); + ref.read(schoolsTabControllerProvider.notifier).searchSchools(); } } } @@ -160,13 +172,14 @@ class ShowOnlyFavoriteSchools extends _$ShowOnlyFavoriteSchools { final filteredSchoolsProvider = Provider.autoDispose>((ref) { final filter = ref.watch(schoolDivisionsProvider); - final schools = ref.watch(schoolsProvider).valueOrNull; + final schools = ref.watch(schoolsTabControllerProvider).valueOrNull; final favoritesIds = ref.watch(favoriteSchoolsProvider); final onlyFavorites = ref.watch(showOnlyFavoriteSchoolsProvider); if (schools == null) return ImmutableList(const []); + return ImmutableList([ for (final school in schools) - if (filter[school.currentDivision]! && + if ((filter[school.currentDivision] ?? false) && (!onlyFavorites || favoritesIds.contains('${school.id}'))) school, ]); diff --git a/lib/features/schools/schools_tab_providers.g.dart b/lib/features/schools/schools_tab_controller.g.dart similarity index 81% rename from lib/features/schools/schools_tab_providers.g.dart rename to lib/features/schools/schools_tab_controller.g.dart index 56b5ab5..d6bca2b 100644 --- a/lib/features/schools/schools_tab_providers.g.dart +++ b/lib/features/schools/schools_tab_controller.g.dart @@ -1,27 +1,30 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'schools_tab_providers.dart'; +part of 'schools_tab_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$schoolsHash() => r'4aba4f933e856cc38139fc9d9b77abbb5496fbeb'; +String _$schoolsTabControllerHash() => + r'86239ab4bdf4f3bbf10ab430895ff410a2efcafe'; -/// See also [Schools]. -@ProviderFor(Schools) -final schoolsProvider = - AutoDisposeAsyncNotifierProvider>.internal( - Schools.new, - name: r'schoolsProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$schoolsHash, +/// See also [SchoolsTabController]. +@ProviderFor(SchoolsTabController) +final schoolsTabControllerProvider = AutoDisposeAsyncNotifierProvider< + SchoolsTabController, ImmutableList>.internal( + SchoolsTabController.new, + name: r'schoolsTabControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$schoolsTabControllerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$Schools = AutoDisposeAsyncNotifier>; -String _$favoriteSchoolsHash() => r'8acf3025d87137d207d0871badbc5b455f90d8c6'; +typedef _$SchoolsTabController + = AutoDisposeAsyncNotifier>; +String _$favoriteSchoolsHash() => r'bbf5471b1c45dfd748df53c883c674586c4d7a3b'; /// See also [FavoriteSchools]. @ProviderFor(FavoriteSchools) @@ -37,7 +40,7 @@ final favoriteSchoolsProvider = AutoDisposeNotifierProvider>; -String _$schoolDivisionsHash() => r'fd0bd47cb45eff1499fdb954f54778f2b22f4347'; +String _$schoolDivisionsHash() => r'a37f839f4ee8d6e192affdb0cb63e1fbabe0aabe'; /// See also [SchoolDivisions]. @ProviderFor(SchoolDivisions) @@ -53,7 +56,7 @@ final schoolDivisionsProvider = AutoDisposeNotifierProvider>; -String _$searchedSchoolHash() => r'1109496d2bb599150694d30dc372748c439e63bc'; +String _$searchedSchoolHash() => r'23d65b61351b411c9230f5f87ffe7ba56348b0a6'; /// See also [SearchedSchool]. @ProviderFor(SearchedSchool) diff --git a/lib/features/schools/schools_tab_page.dart b/lib/features/schools/schools_tab_page.dart index 1b3c5b4..838d6f4 100644 --- a/lib/features/schools/schools_tab_page.dart +++ b/lib/features/schools/schools_tab_page.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../common_widgets/app_loading_indicator.dart'; import '../../utils/debouncer.dart'; import '../home/home_page_controller.dart'; -import 'schools_tab_providers.dart'; +import 'schools_tab_controller.dart'; import 'widgets/school_filter_chips.dart'; import 'widgets/schools_tab_body.dart'; import 'widgets/schools_tab_navbar.dart'; @@ -21,29 +21,23 @@ class SchoolsTabPage extends ConsumerStatefulWidget { class _SchoolsTabState extends ConsumerState { final _debouncer = Debouncer(defaultDelay); - ScrollController? controller; + late final ScrollController controller = PrimaryScrollController.of(context); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - controller = PrimaryScrollController.of(context); - controller?.addListener(_loadMoreListener); + controller.addListener(_loadMoreListener); }); } - @override - void dispose() { - _debouncer.dispose(); - controller?.removeListener(_loadMoreListener); - super.dispose(); - } - void _loadMoreListener() { - final position = controller!.position; + final position = controller.position; if (position.pixels == position.maxScrollExtent) { if (!ref.read(schoolReachedMaxProvider)) { - _debouncer.run(ref.read(schoolsProvider.notifier).fetchNextPage); + _debouncer.run( + ref.read(schoolsTabControllerProvider.notifier).fetchNextPage, + ); } } } @@ -51,6 +45,7 @@ class _SchoolsTabState extends ConsumerState { @override Widget build(BuildContext context) { final focus = FocusScope.of(context); + return Scaffold( body: GestureDetector( onTap: () => focus.hasFocus ? focus.unfocus() : null, @@ -67,6 +62,13 @@ class _SchoolsTabState extends ConsumerState { ), ); } + + @override + void dispose() { + _debouncer.dispose(); + controller.removeListener(_loadMoreListener); + super.dispose(); + } } class SchoolsTabLoadMoreIndicator extends ConsumerWidget { @@ -77,7 +79,8 @@ class SchoolsTabLoadMoreIndicator extends ConsumerWidget { final reachedLimit = ref.watch(schoolReachedMaxProvider); final isNotEmpty = ref.watch(filteredSchoolsProvider).isNotEmpty; final isFiltered = ref.watch(filteredSchoolsProvider).length != - ref.watch(schoolsProvider).valueOrNull?.length; + ref.watch(schoolsTabControllerProvider).valueOrNull?.length; + return AppLoadingIndicator( showLoading: !reachedLimit && isNotEmpty && !isFiltered, sliver: true, diff --git a/lib/features/schools/widgets/school_card.dart b/lib/features/schools/widgets/school_card.dart index 52baaf6..7de31e4 100644 --- a/lib/features/schools/widgets/school_card.dart +++ b/lib/features/schools/widgets/school_card.dart @@ -5,12 +5,12 @@ import 'package:go_router/go_router.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/intl_extension.dart'; -import '../../../core/extensions/string_extension.dart'; +import '../../../core/extensions/string_extensions.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../school.dart'; import '../school_extensions.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; import 'school_flag.dart'; class SchoolCard extends ConsumerStatefulWidget { @@ -32,6 +32,7 @@ class _SchoolCardState extends ConsumerState { @override Widget build(BuildContext context) { final school = ref.watch(currentSchoolProvider); + return Padding( padding: widget.margin ?? const EdgeInsets.only(bottom: 4), child: Card( @@ -47,7 +48,7 @@ class _SchoolCardState extends ConsumerState { onLongPress: school.name == school.translatedName ? null : () => showOriginal.value = !showOriginal.value, - child: Container( + child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, @@ -125,19 +126,19 @@ class SchoolInfoCard extends StatelessWidget { '${school.translatedName}' '${context.screenSize.isLarge ? '\n' : ' '}', maxLines: 2, - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), + style: context.titleLarge.copyWith( + color: colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), ) : Text( '${school.name}' '${!context.screenSize.isLarge ? '\n' : ' '}', maxLines: 2, - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold, - ), + style: context.titleLarge.copyWith( + color: colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -164,9 +165,9 @@ class SchoolInfoCard extends StatelessWidget { ), maxLines: 2, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: colorScheme.onSurface, - ), + style: context.titleMedium.copyWith( + color: colorScheme.onSurface, + ), ), ), ), @@ -174,16 +175,16 @@ class SchoolInfoCard extends StatelessWidget { child: Consumer( builder: (context, ref, child) { final sort = ref.watch(selectedSchoolSortProvider); + return Padding( padding: const EdgeInsets.only(bottom: 8, right: 8), child: TextButton( onPressed: null, style: TextButton.styleFrom( - textStyle: - Theme.of(context).textTheme.labelLarge!.copyWith( - wordSpacing: -1, - letterSpacing: -0.5, - ), + textStyle: context.labelLarge.copyWith( + wordSpacing: -1, + letterSpacing: -0.5, + ), ), child: Text.rich( TextSpan( @@ -212,6 +213,7 @@ class SchoolInfoCard extends StatelessWidget { extension SelectedSchoolSortExtension on SchoolSort { String getSortedValue(School school, BuildContext context) { + final foundation = school.foundationDate; switch (this) { case SchoolSort.lastPerformance: if (school.lastPosition == 0) { @@ -223,8 +225,8 @@ extension SelectedSchoolSortExtension on SchoolSort { case SchoolSort.name: case SchoolSort.foundationDate: case SchoolSort.location: - if (school.foundationDate != null) { - return school.foundationDate!.intlShort(context); + if (foundation != null) { + return foundation.intlShort(context); } return ''; } diff --git a/lib/features/schools/widgets/school_filter_chips.dart b/lib/features/schools/widgets/school_filter_chips.dart index d716271..0df5038 100644 --- a/lib/features/schools/widgets/school_filter_chips.dart +++ b/lib/features/schools/widgets/school_filter_chips.dart @@ -7,7 +7,7 @@ import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../school_extensions.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; class SchoolFilterChips extends ConsumerWidget { const SchoolFilterChips({ @@ -21,6 +21,7 @@ class SchoolFilterChips extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final selectedDivisions = ref.watch(schoolDivisionsProvider); final padding = MediaQuery.paddingOf(context); + return SliverCrossAxisConstrained( maxCrossAxisExtent: ScreenSize.lg.value, child: SliverToBoxAdapter( diff --git a/lib/features/schools/widgets/school_flag.dart b/lib/features/schools/widgets/school_flag.dart index bc57e45..54dfde1 100644 --- a/lib/features/schools/widgets/school_flag.dart +++ b/lib/features/schools/widgets/school_flag.dart @@ -6,7 +6,7 @@ import '../../../common_widgets/app_fade_in_image.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; import '../school.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; class SchoolFlag extends ConsumerWidget { const SchoolFlag({ @@ -27,6 +27,7 @@ class SchoolFlag extends ConsumerWidget { final isFavorite = ref.watch( favoriteSchoolsProvider.select((v) => v.contains('${school.id}')), ); + return Stack( children: [ ClipRRect( @@ -136,7 +137,7 @@ class EmptyImage extends StatelessWidget { const SizedBox(height: 8), Text( context.loc.noImage, - style: context.textTheme.titleLarge!.copyWith( + style: context.titleLarge.copyWith( color: context.colorScheme.onPrimaryContainer, ), ), diff --git a/lib/features/schools/widgets/schools_empty_list.dart b/lib/features/schools/widgets/schools_empty_list.dart index 20cecdd..cde75b8 100644 --- a/lib/features/schools/widgets/schools_empty_list.dart +++ b/lib/features/schools/widgets/schools_empty_list.dart @@ -3,13 +3,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/theme_of_context_extension.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; class SchoolsEmptyList extends ConsumerWidget { const SchoolsEmptyList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final schools = ref.watch(schoolsTabControllerProvider).value; + return SliverFillRemaining( child: Center( child: Padding( @@ -17,11 +19,11 @@ class SchoolsEmptyList extends ConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (ref.watch(schoolsProvider).value?.isEmpty ?? false) ...[ + if (schools == null || schools.isEmpty) ...[ const SizedBox(height: 8), Text( context.loc.noSchoolsFound, - style: context.textTheme.titleMedium!.copyWith( + style: context.titleMedium.copyWith( color: context.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, @@ -31,7 +33,7 @@ class SchoolsEmptyList extends ConsumerWidget { if (ref.watch(favoriteSchoolsProvider).isEmpty) Text( context.loc.noFavoriteSchools, - style: context.textTheme.titleMedium!.copyWith( + style: context.titleMedium.copyWith( color: context.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, @@ -39,7 +41,7 @@ class SchoolsEmptyList extends ConsumerWidget { else Text( context.loc.noFilteredSchools, - style: context.textTheme.titleMedium!.copyWith( + style: context.titleMedium.copyWith( color: context.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, diff --git a/lib/features/schools/widgets/schools_tab_body.dart b/lib/features/schools/widgets/schools_tab_body.dart index 6a6ccb2..17bc882 100644 --- a/lib/features/schools/widgets/schools_tab_body.dart +++ b/lib/features/schools/widgets/schools_tab_body.dart @@ -10,7 +10,7 @@ import '../../../core/extensions/js_bottom_padding_extension.dart' import '../../../utils/immutable_list.dart'; import '../../../utils/screen_size.dart'; import '../school.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; import 'school_card.dart'; import 'schools_empty_list.dart'; @@ -22,11 +22,12 @@ class SchoolsTabBody extends ConsumerWidget { return SliverSafeArea( top: false, sliver: AppAsyncSliverWidget( - asyncValue: ref.watch(schoolsProvider), - onErrorRetry: () => ref.invalidate(schoolsProvider), + asyncValue: ref.watch(schoolsTabControllerProvider), + onErrorRetry: () => ref.invalidate(schoolsTabControllerProvider), child: (value) => Consumer( builder: (context, ref, child) { final schools = ref.watch(filteredSchoolsProvider); + return SliverCrossAxisConstrained( maxCrossAxisExtent: ScreenSize.lg.value, child: SliverPadding( @@ -61,9 +62,10 @@ class SliverSchoolsList extends StatelessWidget { Widget build(BuildContext context) { return SliverDynamicHeightGridView( itemCount: schools.length, - crossAxisCount: gridCrossAxisCount(context), + crossAxisCount: context.screenSize.defaultCrossAxisCount, builder: (context, index) { final school = schools[index]; + return AppAnimationWrapper( child: ProviderScope( overrides: [ @@ -75,12 +77,4 @@ class SliverSchoolsList extends StatelessWidget { }, ); } - - static int gridCrossAxisCount(BuildContext context) { - return switch (context.screenSize) { - ScreenSize.xs => 1, - ScreenSize.md => 2, - ScreenSize.lg => 3 - }; - } } diff --git a/lib/features/schools/widgets/schools_tab_navbar.dart b/lib/features/schools/widgets/schools_tab_navbar.dart index 4fb7195..3fd43e2 100644 --- a/lib/features/schools/widgets/schools_tab_navbar.dart +++ b/lib/features/schools/widgets/schools_tab_navbar.dart @@ -6,6 +6,7 @@ import '../../../common_widgets/app_cupertino_button.dart'; import '../../../common_widgets/app_cupertino_sliver_navigation_bar.dart'; import '../../../core/extensions/app_localization_extension.dart'; import '../../../core/extensions/hardcoded_extension.dart'; +import '../../../utils/app_loggers.dart'; import '../../../utils/screen_size.dart'; class SchoolsTabNavBar extends StatelessWidget { @@ -26,7 +27,9 @@ class SchoolsTabNavBar extends StatelessWidget { PullDownMenuItem.selectable( title: '🇧🇷 Rio de Janeiro'.hardcoded, selected: true, - onTap: () {}, + onTap: () { + logViews.info('Selected Rio de Janeiro'); + }, ), ], buttonBuilder: (context, showMenu) => AppCupertinoButton( diff --git a/lib/features/schools/widgets/schools_tab_search_header.dart b/lib/features/schools/widgets/schools_tab_search_header.dart index b4763c5..b3a4e86 100644 --- a/lib/features/schools/widgets/schools_tab_search_header.dart +++ b/lib/features/schools/widgets/schools_tab_search_header.dart @@ -10,7 +10,7 @@ import '../../../core/extensions/theme_of_context_extension.dart'; import '../../../utils/screen_size.dart'; import '../../home/widgets/adaptive_navigation_rail.dart'; import '../school.dart'; -import '../schools_tab_providers.dart'; +import '../schools_tab_controller.dart'; class SchoolsTabSearchHeader extends ConsumerStatefulWidget { const SchoolsTabSearchHeader({super.key}); @@ -24,16 +24,11 @@ class _SchoolsTabSearchHeaderState extends ConsumerState { late final controller = TextEditingController(); - @override - void dispose() { - super.dispose(); - controller.dispose(); - } - @override Widget build(BuildContext context) { // Watch to not dispose the provider ref.watch(searchedSchoolProvider); + return WebPaddingSliver.only( right: true, sliver: SliverSafeArea( @@ -112,14 +107,24 @@ class _SchoolsTabSearchHeaderState ); } + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + Rect calculateRect(BuildContext context) { + const top = 100.0; + const height = 48.0; if (context.screenSize.isLarge) { final left = ScreenSize.lg.value + AdaptiveNavigationRail.largeRailWidth; + const largeWidth = -48.0; - return Rect.fromLTWH(left, 100, -48, 48); + return Rect.fromLTWH(left, top, largeWidth, height); } + const width = 48.0; final left = MediaQuery.sizeOf(context).width - 48; - return Rect.fromLTWH(left, 100, 48, 48); + return Rect.fromLTWH(left, top, width, height); } } diff --git a/lib/initialization.dart b/lib/initialization.dart index 71a9d7f..baeea1b 100644 --- a/lib/initialization.dart +++ b/lib/initialization.dart @@ -3,21 +3,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'core/extensions/app_localization_extension.dart'; -import 'core/providers/initialization_provider.dart'; -import 'core/theme/theme_provider.dart'; +import 'core/providers/initialization.dart'; +import 'core/theme/theme_mode_controller.dart'; import 'features/home/widgets/adaptive_navigation_rail.dart'; import 'utils/screen_size.dart'; -class InitializationPage extends ConsumerWidget { - const InitializationPage({required this.onLoaded, super.key}); +class Initialization extends ConsumerWidget { + const Initialization({required this.onLoaded, super.key}); final WidgetBuilder onLoaded; static const path = '/startup'; @override Widget build(BuildContext context, WidgetRef ref) { - ref.watch(appThemeModeProvider); + ref.watch(themeModeControllerProvider); final initProvider = ref.watch(initializationProvider); + return AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: switch (initProvider) { @@ -40,6 +41,7 @@ class AppStartupLoadingWidget extends StatelessWidget { @override Widget build(BuildContext context) { final screenSize = context.screenSize; + return Scaffold( appBar: AppBar( elevation: 0, diff --git a/lib/localization/language.dart b/lib/localization/language.dart index 53d4868..4a36139 100644 --- a/lib/localization/language.dart +++ b/lib/localization/language.dart @@ -46,6 +46,7 @@ extension LanguageExtension on Language { bool get isSameAsPlatform { final platformLanguage = WidgetsBinding.instance.platformDispatcher; + return languageCode == platformLanguage.locale.languageCode; } } diff --git a/lib/localization/language_app_provider.dart b/lib/localization/language_app_controller.dart similarity index 69% rename from lib/localization/language_app_provider.dart rename to lib/localization/language_app_controller.dart index 49e9478..96202ea 100644 --- a/lib/localization/language_app_provider.dart +++ b/lib/localization/language_app_controller.dart @@ -1,17 +1,18 @@ import 'package:flutter/widgets.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../core/providers/prefs_provider.dart'; +import '../core/providers/prefs.dart'; import 'language.dart'; -part 'language_app_provider.g.dart'; +part 'language_app_controller.g.dart'; @Riverpod(keepAlive: true) -class LanguageApp extends _$LanguageApp { +class LanguageAppController extends _$LanguageAppController { @override FutureOr build() async { final prefs = await ref.watch(prefsProvider.future); final localeKey = prefs.getString('locale') ?? WidgetsBinding.instance.platformDispatcher.locale.languageCode; + return Language.values.firstWhere( (e) => e.languageCode == localeKey, orElse: () => Language.en, @@ -21,11 +22,11 @@ class LanguageApp extends _$LanguageApp { Future setLanguage( Language language, ) async { - final prefs = ref.read(prefsProvider).value; + final prefs = await ref.read(prefsProvider.future); if (language.isSameAsPlatform) { - await prefs!.remove('locale'); + await prefs.remove('locale'); } else { - await prefs!.setString('locale', language.languageCode); + await prefs.setString('locale', language.languageCode); } state = AsyncData(language); } diff --git a/lib/features/instruments/instruments_tab_providers.g.dart b/lib/localization/language_app_controller.g.dart similarity index 59% rename from lib/features/instruments/instruments_tab_providers.g.dart rename to lib/localization/language_app_controller.g.dart index 55a7d23..20d7237 100644 --- a/lib/features/instruments/instruments_tab_providers.g.dart +++ b/lib/localization/language_app_controller.g.dart @@ -1,26 +1,27 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'instruments_tab_providers.dart'; +part of 'language_app_controller.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$instrumentsTabHash() => r'7e4f3788aa70d663dfd0ab1363840ff64e8bd1d1'; +String _$languageAppControllerHash() => + r'b305e4beb2aa7ffa06066585974ffbabde50577d'; -/// See also [InstrumentsTab]. -@ProviderFor(InstrumentsTab) -final instrumentsTabProvider = AutoDisposeAsyncNotifierProvider>.internal( - InstrumentsTab.new, - name: r'instrumentsTabProvider', +/// See also [LanguageAppController]. +@ProviderFor(LanguageAppController) +final languageAppControllerProvider = + AsyncNotifierProvider.internal( + LanguageAppController.new, + name: r'languageAppControllerProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$instrumentsTabHash, + : _$languageAppControllerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$InstrumentsTab = AutoDisposeAsyncNotifier>; +typedef _$LanguageAppController = AsyncNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/localization/language_app_provider.g.dart b/lib/localization/language_app_provider.g.dart deleted file mode 100644 index f22a31b..0000000 --- a/lib/localization/language_app_provider.g.dart +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'language_app_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$languageAppHash() => r'ef2e6c2b06254f577831c8b2bdebcff29909220e'; - -/// See also [LanguageApp]. -@ProviderFor(LanguageApp) -final languageAppProvider = - AsyncNotifierProvider.internal( - LanguageApp.new, - name: r'languageAppProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$languageAppHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$LanguageApp = AsyncNotifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/main.dart b/lib/main.dart index 6b97619..4ce1459 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,11 +8,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'core/extensions/app_localization_extension.dart'; -import 'core/theme/theme_data.dart'; -import 'core/theme/theme_provider.dart'; +import 'core/theme/app_theme.dart'; +import 'core/theme/theme_mode_controller.dart'; import 'l10n/app_localizations.dart'; import 'localization/language.dart'; -import 'localization/language_app_provider.dart'; +import 'localization/language_app_controller.dart'; import 'routing/app_router.dart'; void main() { @@ -20,7 +20,7 @@ void main() { runApp(const ProviderScope(child: MainApp())); } -///This widget is the root of your application. +// ignore: prefer_match_file_name class MainApp extends ConsumerWidget { const MainApp({super.key}); @@ -36,9 +36,10 @@ class MainApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { _initAndroid(); final router = ref.watch(appRouterProvider); - final themeMode = ref.watch(appThemeModeProvider); + final themeMode = ref.watch(themeModeControllerProvider); final isTrueBlack = ref.watch(appThemeTrueBlackProvider); - final language = ref.watch(languageAppProvider).valueOrNull; + final language = ref.watch(languageAppControllerProvider).valueOrNull; + return MaterialApp.router( routerConfig: router, onGenerateTitle: (context) => context.loc.appTitle, @@ -57,7 +58,7 @@ class MainApp extends ConsumerWidget { ], builder: (context, child) => MediaQuery.withClampedTextScaling( maxScaleFactor: 2, - child: child!, + child: child ?? const SizedBox.shrink(), ), supportedLocales: AppLocalizations.supportedLocales, locale: language?.locale, diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 5921e13..33e306c 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -1,9 +1,8 @@ -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../common_widgets/cupertino_sheet.dart'; -import '../core/providers/initialization_provider.dart'; +import '../common_widgets/cupertino_sheet_route.dart'; +import '../core/providers/initialization.dart'; import '../features/home/home_page.dart'; import '../features/home/home_page_controller.dart'; import '../features/instruments/details/instrument_details_page.dart'; @@ -19,143 +18,157 @@ part 'app_router.g.dart'; final _rootNavigatorKey = GlobalKey(debugLabel: 'root'); @riverpod -GoRouter appRouter(AppRouterRef ref) { - final controllers = IMap( - {for (final tab in HomeTab.values) tab.name: ScrollController()}, - ); - final initProvider = ref.watch(initializationProvider); - return GoRouter( - navigatorKey: _rootNavigatorKey, - initialLocation: HomeTab.instruments.path, - errorPageBuilder: (context, state) => const NoTransitionPage( - child: Scaffold(body: Text('404 - Not Found')), - ), - redirect: (context, state) { - if (initProvider.isLoading) { - return InitializationPage.path; - } - if (initProvider.hasError) { - return InitializationPage.path; - } - final path = state.fullPath?.split('/'); - final topPath = path?.sublist(0, 2).join('/'); - if (path != null && HomeTab.values.any((v) => v.path == topPath)) { - final home = ref.read(currentTabProvider); - final nextTab = HomeTab.values.firstWhere( - (t) => t.path == topPath, - ); - final top = path.length <= 2; - if (home.tab.path != nextTab.path || home.topRoute != top) { - Future.microtask( - () => ref.read(currentTabProvider.notifier).set(nextTab, top: top), - ); - } - if (home.tab.path == nextTab.path && home.topRoute == top) { - Future.microtask( - () => _scrollTabToTheTop(controllers[nextTab.name]!), - ); - } +class AppRouter extends _$AppRouter { + @override + GoRouter build() { + final controllers = { + for (final tab in HomeTab.values) tab.name: ScrollController(), + }; + ref.watch(initializationProvider); - return null; - } - return null; - }, - routes: [ - GoRoute( - path: InitializationPage.path, - pageBuilder: (context, state) => NoTransitionPage( - child: InitializationPage( - // * Just a placeholder, route will be managed by GoRouter - onLoaded: (_) => const SizedBox.shrink(), + return GoRouter( + navigatorKey: _rootNavigatorKey, + initialLocation: HomeTab.instruments.path, + errorPageBuilder: (context, state) => const NoTransitionPage( + child: Scaffold(body: Text('404 - Not Found')), + ), + redirect: (context, state) => _redirect(state, controllers), + routes: [ + GoRoute( + path: Initialization.path, + pageBuilder: (context, state) => NoTransitionPage( + child: Initialization( + // * Just a placeholder, route will be managed by GoRouter + onLoaded: (_) => const SizedBox.shrink(), + ), ), ), - ), - StatefulShellRoute.indexedStack( - pageBuilder: (_, __, shell) => NoTransitionPage(child: HomePage(shell)), - branches: [ - StatefulShellBranch( - routes: [ - GoRoute( - path: InstrumentsTabPage.path, - builder: (_, __) => PrimaryScrollController( - controller: controllers[InstrumentsTabPage.tab.name]!, - child: const InstrumentsTabPage(), - ), - routes: [ - GoRoute( - path: InstrumentDetailsPage.path, - onExit: (context) { - Future.microtask( - () => ref - .read(currentTabProvider.notifier) - .set(InstrumentsTabPage.tab, top: true), - ); - return Future.value(true); - }, - builder: (_, state) => InstrumentDetailsPage( - id: int.parse(state.pathParameters['id']!), - ), + StatefulShellRoute.indexedStack( + pageBuilder: (_, __, shell) => + NoTransitionPage(child: HomePage(shell)), + branches: [ + StatefulShellBranch( + routes: [ + GoRoute( + path: InstrumentsTabPage.path, + builder: (_, __) => PrimaryScrollController( + controller: controllers[InstrumentsTabPage.tab.name]!, + child: const InstrumentsTabPage(), ), - ], - ), - ], - ), - StatefulShellBranch( - routes: [ - GoRoute( - path: ParadesTabPage.path, - builder: (_, __) => PrimaryScrollController( - controller: controllers[ParadesTabPage.tab.name]!, - child: const ParadesTabPage(), + routes: [ + GoRoute( + path: InstrumentDetailsPage.path, + onExit: (context) { + Future.microtask( + () => ref + .read(homePageControllerProvider.notifier) + .set(InstrumentsTabPage.tab, top: true), + ); + + return Future.value(true); + }, + builder: (_, state) => InstrumentDetailsPage( + id: int.parse(state.pathParameters['id']!), + ), + ), + ], ), - ), - ], - ), - StatefulShellBranch( - routes: [ - GoRoute( - path: SchoolsTabPage.path, - builder: (context, state) { - return PrimaryScrollController( - controller: controllers[SchoolsTabPage.tab.name]!, - child: const SchoolsTabPage(), - ); - }, - routes: [ - GoRoute( - path: SchoolDetailsPage.path, - pageBuilder: (context, state) { - return AppCupertinoSheetPage( - child: SchoolDetailsPage( - id: int.parse(state.pathParameters['id']!), - ), - ); - }, - onExit: (context) { - Future.microtask( - () => ref - .read(currentTabProvider.notifier) - .set(SchoolsTabPage.tab, top: true), - ); - return Future.value(true); - }, + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: ParadesTabPage.path, + builder: (_, __) => PrimaryScrollController( + controller: controllers[ParadesTabPage.tab.name]!, + child: const ParadesTabPage(), ), - ], - ), - ], - ), - ], - ), - ], - ); -} + ), + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: SchoolsTabPage.path, + builder: (context, state) { + return PrimaryScrollController( + controller: controllers[SchoolsTabPage.tab.name]!, + child: const SchoolsTabPage(), + ); + }, + routes: [ + GoRoute( + path: SchoolDetailsPage.path, + pageBuilder: (context, state) { + return AppCupertinoSheetPage( + child: SchoolDetailsPage( + id: int.parse(state.pathParameters['id']!), + ), + ); + }, + onExit: (context) { + Future.microtask( + () => ref + .read(homePageControllerProvider.notifier) + .set(SchoolsTabPage.tab, top: true), + ); -void _scrollTabToTheTop(ScrollController controller) { - if (controller.hasClients) { - controller.animateTo( - 0, - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, + return Future.value(true); + }, + ), + ], + ), + ], + ), + ], + ), + ], ); } + + void _scrollTabToTheTop(ScrollController controller) { + if (controller.hasClients) { + controller.animateTo( + 0, + duration: const Duration(milliseconds: 350), + curve: Curves.easeInOut, + ); + } + } + + FutureOr _redirect( + GoRouterState state, + Map controllers, + ) async { + final init = ref.watch(initializationProvider); + if (init.isLoading) { + return Initialization.path; + } + if (init.hasError) { + return Initialization.path; + } + final path = state.fullPath?.split('/'); + final topPath = path?.sublist(0, 2).join('/'); + if (path != null && HomeTab.values.any((v) => v.path == topPath)) { + final home = ref.read(homePageControllerProvider); + final nextTab = HomeTab.values.firstWhere((t) => t.path == topPath); + final top = path.length <= 2; + if (home.tab.path != nextTab.path || home.topRoute != top) { + await Future.microtask( + () => ref + .read(homePageControllerProvider.notifier) + .set(nextTab, top: top), + ); + } + if (home.tab.path == nextTab.path && home.topRoute == top) { + await Future.microtask( + () => _scrollTabToTheTop(controllers[nextTab.name]!), + ); + } + + return null; + } + + return null; + } } diff --git a/lib/routing/app_router.g.dart b/lib/routing/app_router.g.dart index fa0121f..df26be6 100644 --- a/lib/routing/app_router.g.dart +++ b/lib/routing/app_router.g.dart @@ -6,12 +6,13 @@ part of 'app_router.dart'; // RiverpodGenerator // ************************************************************************** -String _$appRouterHash() => r'a6c0ef4349db1a9cfbdfc28c33aeb1449843c097'; +String _$appRouterHash() => r'9eba2f50346adf432762ce32a67d505104b7374e'; -/// See also [appRouter]. -@ProviderFor(appRouter) -final appRouterProvider = AutoDisposeProvider.internal( - appRouter, +/// See also [AppRouter]. +@ProviderFor(AppRouter) +final appRouterProvider = + AutoDisposeNotifierProvider.internal( + AppRouter.new, name: r'appRouterProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : _$appRouterHash, @@ -19,6 +20,6 @@ final appRouterProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); -typedef AppRouterRef = AutoDisposeProviderRef; +typedef _$AppRouter = AutoDisposeNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/routing/refresh_listenable.dart b/lib/routing/refresh_listenable.dart index 8c9c433..74d2fdd 100644 --- a/lib/routing/refresh_listenable.dart +++ b/lib/routing/refresh_listenable.dart @@ -3,16 +3,17 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; /// This class was imported from the migration guide for GoRouter 5.0 -class GoRouterRefreshStream extends ChangeNotifier { - GoRouterRefreshStream(Stream stream) { +class RefreshListenable extends ChangeNotifier { + // ignore: avoid_late_keyword + late final StreamSubscription _subscription; + + RefreshListenable(Stream stream) { notifyListeners(); _subscription = stream.asBroadcastStream().listen( (dynamic _) => notifyListeners(), ); } - late final StreamSubscription _subscription; - @override void dispose() { _subscription.cancel(); diff --git a/lib/utils/app_error_handler.dart b/lib/utils/app_error_handler.dart index 10c52ad..4574fee 100644 --- a/lib/utils/app_error_handler.dart +++ b/lib/utils/app_error_handler.dart @@ -11,6 +11,7 @@ void registerErrorHandlers() { // * Handle errors from the underlying platform/OS PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { debugPrint(error.toString()); + return true; }; // * Show some error UI when any widget in the app fails to build diff --git a/lib/utils/app_loggers.dart b/lib/utils/app_loggers.dart index f3665b9..18e4d71 100644 --- a/lib/utils/app_loggers.dart +++ b/lib/utils/app_loggers.dart @@ -47,9 +47,10 @@ void deactivateLoggers(Set loggers) { } void _printLog(LogRecord record) { + const padLimit = 3; print( '(${record.time.second}.' - '${record.time.millisecond.toString().padLeft(3, '0')})' + '${record.time.millisecond.toString().padLeft(padLimit, '0')})' ' ${record.loggerName} > ${record.level.name}: ${record.message}', ); } diff --git a/lib/utils/debouncer.dart b/lib/utils/debouncer.dart index 146f777..c3cc382 100644 --- a/lib/utils/debouncer.dart +++ b/lib/utils/debouncer.dart @@ -4,10 +4,10 @@ import 'dart:async'; const defaultDelay = Duration(milliseconds: 300); class Debouncer { - Debouncer(this.delay); final Duration delay; Timer? _timer; bool _isFirstCall = true; + Debouncer(this.delay); void run(void Function() action) { if (_isFirstCall) { diff --git a/lib/utils/screen_size.dart b/lib/utils/screen_size.dart index 49fe54e..519996f 100644 --- a/lib/utils/screen_size.dart +++ b/lib/utils/screen_size.dart @@ -7,6 +7,10 @@ const _smallWidth = 600.0; const _mediumWidth = 900.0; const _largeWidth = 1200.0; +const _smallAxisCount = 1; +const _mediumAxisCount = 2; +const _largeAxisCount = 3; + enum ScreenSize { xs, md, @@ -22,6 +26,12 @@ enum ScreenSize { (ScreenSize.lg) => _largeWidth, }; + int get defaultCrossAxisCount => switch (this) { + (ScreenSize.xs) => _smallAxisCount, + (ScreenSize.md) => _mediumAxisCount, + (ScreenSize.lg) => _largeAxisCount, + }; + static double get smallHeight => _smallHeight; } @@ -30,6 +40,10 @@ extension MediaQueryExtension on BuildContext { final width = MediaQuery.sizeOf(this).width; if (width < ScreenSize.xs.value) return ScreenSize.xs; if (width < ScreenSize.md.value) return ScreenSize.md; + return ScreenSize.lg; } + + bool get isSmallHeight => + MediaQuery.sizeOf(this).height < ScreenSize.smallHeight; } diff --git a/test/core/app_provider_test.dart b/test/core/app_provider_test.dart deleted file mode 100644 index d313a54..0000000 --- a/test/core/app_provider_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -// ignore_for_file: invalid_use_of_visible_for_overriding_member - -import 'package:batucadapp/core/app_provider.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockAppParent extends AsyncNotifier - with Mock - implements AppParent {} - -class MockApp extends AsyncNotifier with Mock implements App {} - -void main() { - test('Provider return the desired string', () async { - final mockAppParent = MockAppParent(); - - when(mockAppParent.build).thenReturn(const AppModel('AppParent')); - - final container = createContainer( - overrides: [ - appParentProvider.overrideWith(() => mockAppParent), - appProvider.overrideWith(App.new), - ], - ); - final provider = await container.read(appProvider.future); - expect(provider.name, 'AppParent'); - }); -} - -ProviderContainer createContainer({ - ProviderContainer? parent, - List overrides = const [], - List? observers, -}) { - // Create a ProviderContainer, and optionally allow specifying parameters. - final container = ProviderContainer( - parent: parent, - overrides: overrides, - observers: observers, - ); - - // When the test ends, dispose the container. - addTearDown(container.dispose); - - return container; -} diff --git a/test/core/providers/client_network_provider_test.dart b/test/core/providers/client_network_provider_test.dart index b72f069..976f6ba 100644 --- a/test/core/providers/client_network_provider_test.dart +++ b/test/core/providers/client_network_provider_test.dart @@ -1,7 +1,8 @@ -// ignore_for_file: depend_on_referenced_packages, invalid_use_of_visible_for_overriding_member +// ignore_for_file: depend_on_referenced_packages +// ignore_for_file: invalid_use_of_visible_for_overriding_member import 'package:batucadapp/constants.dart'; -import 'package:batucadapp/core/providers/client_network_provider.dart'; +import 'package:batucadapp/core/providers/client_network.dart'; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -21,12 +22,13 @@ class MockClientNetwork extends AsyncNotifier implements ClientNetwork {} void main() { + const dioOkResponseCode = 200; group('ClientNetwork build', () { test('Dio is configured with correct base URL and language', () async { final baseOptions = BaseOptions( baseUrl: Endpoint.basePath.path, - connectTimeout: AppConstants.connectTimeout, - receiveTimeout: AppConstants.receiveTimeout, + connectTimeout: Constants.connectTimeout, + receiveTimeout: Constants.receiveTimeout, queryParameters: {'language': 'en'}, ); final dio = Dio(baseOptions); @@ -39,7 +41,7 @@ void main() { final readDio = await container.read(clientNetworkProvider.future); DioAdapter(dio: readDio).onGet( Endpoint.basePath.path, - (server) => server.reply(200, 'OK'), + (server) => server.reply(dioOkResponseCode, 'OK'), ); final response = await readDio.get(Endpoint.basePath.path); diff --git a/test/core/providers/prefs_provider_test.dart b/test/core/providers/prefs_provider_test.dart index 1e21e3e..b3e2aaf 100644 --- a/test/core/providers/prefs_provider_test.dart +++ b/test/core/providers/prefs_provider_test.dart @@ -1,4 +1,4 @@ -import 'package:batucadapp/core/providers/prefs_provider.dart'; +import 'package:batucadapp/core/providers/prefs.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -10,6 +10,7 @@ void main() { overrides: [prefsProvider.overrideWith((_) => prefs)], ); addTearDown(container.dispose); + return container; } @@ -49,6 +50,7 @@ class MockSharedPreferences extends Mock implements SharedPreferences { throw UnimplementedError('Type ${value.runtimeType} not implemented'); } } + return mock; } } diff --git a/test/core/theme/theme_provider_test.dart b/test/core/theme/theme_provider_test.dart index 0ede6f7..39c2f3d 100644 --- a/test/core/theme/theme_provider_test.dart +++ b/test/core/theme/theme_provider_test.dart @@ -1,5 +1,5 @@ -import 'package:batucadapp/core/providers/prefs_provider.dart'; -import 'package:batucadapp/core/theme/theme_provider.dart'; +import 'package:batucadapp/core/providers/prefs.dart'; +import 'package:batucadapp/core/theme/theme_mode_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -15,10 +15,11 @@ void main() { overrides: [ if (mockSharedPreferences != null) prefsProvider.overrideWith((_) => mockSharedPreferences), - appThemeModeProvider.overrideWith(AppThemeMode.new), + themeModeControllerProvider.overrideWith(ThemeModeController.new), ], ); addTearDown(container.dispose); + return container; } @@ -61,13 +62,13 @@ void main() { test(' ThemeMode.system when SharedPreferences has no value', () async { final mockSharedPreferences = MockSharedPreferences(); final container = makeProviderContainer(mockSharedPreferences); - final themeMode = container.read(appThemeModeProvider); + final themeMode = container.read(themeModeControllerProvider); expect(themeMode, ThemeMode.system); }); test(' ThemeMode.system when SharedPreferences is not init', () async { final container = makeProviderContainer(null); - final themeMode = container.read(appThemeModeProvider); + final themeMode = container.read(themeModeControllerProvider); expect(themeMode, ThemeMode.system); }); @@ -76,7 +77,7 @@ void main() { when(() => mockSharedPreferences.getString('theme_mode')) .thenReturn('light'); final container = makeProviderContainer(mockSharedPreferences); - final themeMode = container.read(appThemeModeProvider); + final themeMode = container.read(themeModeControllerProvider); expect(themeMode, ThemeMode.light); }); @@ -85,7 +86,7 @@ void main() { when(() => mockSharedPreferences.getString('theme_mode')) .thenReturn('dark'); final container = makeProviderContainer(mockSharedPreferences); - final themeMode = container.read(appThemeModeProvider); + final themeMode = container.read(themeModeControllerProvider); expect(themeMode, ThemeMode.dark); }); }); @@ -96,10 +97,12 @@ void main() { when(() => mockSharedPreferences.remove('theme_mode')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - expect(container.read(appThemeModeProvider), ThemeMode.system); - container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.system); + expect(container.read(themeModeControllerProvider), ThemeMode.system); + container + .read(themeModeControllerProvider.notifier) + .setTheme(ThemeMode.system); verify(() => mockSharedPreferences.remove('theme_mode')).called(1); - expect(container.read(appThemeModeProvider), ThemeMode.system); + expect(container.read(themeModeControllerProvider), ThemeMode.system); }); test('Set ThemeMode.light', () async { @@ -107,10 +110,12 @@ void main() { when(() => mockSharedPreferences.setString('theme_mode', 'light')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.light); + container + .read(themeModeControllerProvider.notifier) + .setTheme(ThemeMode.light); verify(() => mockSharedPreferences.setString('theme_mode', 'light')) .called(1); - expect(container.read(appThemeModeProvider), ThemeMode.light); + expect(container.read(themeModeControllerProvider), ThemeMode.light); }); test('Set ThemeMode.dark', () async { @@ -118,10 +123,12 @@ void main() { when(() => mockSharedPreferences.setString('theme_mode', 'dark')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - container.read(appThemeModeProvider.notifier).setTheme(ThemeMode.dark); + container + .read(themeModeControllerProvider.notifier) + .setTheme(ThemeMode.dark); verify(() => mockSharedPreferences.setString('theme_mode', 'dark')) .called(1); - expect(container.read(appThemeModeProvider), ThemeMode.dark); + expect(container.read(themeModeControllerProvider), ThemeMode.dark); }); }); @@ -133,8 +140,8 @@ void main() { when(() => mockSharedPreferences.remove('true_black')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - await container.read(appThemeModeProvider.notifier).toggleTheme(); - expect(container.read(appThemeModeProvider), ThemeMode.light); + await container.read(themeModeControllerProvider.notifier).toggleTheme(); + expect(container.read(themeModeControllerProvider), ThemeMode.light); }); test('Toggle theme from ThemeMode.light', () async { @@ -144,8 +151,8 @@ void main() { when(() => mockSharedPreferences.setString('theme_mode', 'dark')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - await container.read(appThemeModeProvider.notifier).toggleTheme(); - expect(container.read(appThemeModeProvider), ThemeMode.dark); + await container.read(themeModeControllerProvider.notifier).toggleTheme(); + expect(container.read(themeModeControllerProvider), ThemeMode.dark); }); test('Toggle theme from ThemeMode.dark', () async { @@ -155,8 +162,8 @@ void main() { when(() => mockSharedPreferences.remove('theme_mode')) .thenAnswer((_) async => true); final container = makeProviderContainer(mockSharedPreferences); - await container.read(appThemeModeProvider.notifier).toggleTheme(); - expect(container.read(appThemeModeProvider), ThemeMode.system); + await container.read(themeModeControllerProvider.notifier).toggleTheme(); + expect(container.read(themeModeControllerProvider), ThemeMode.system); }); }); } diff --git a/test/localization/language_app_provider_test.dart b/test/localization/language_app_provider_test.dart index 9f59146..b66b0e1 100644 --- a/test/localization/language_app_provider_test.dart +++ b/test/localization/language_app_provider_test.dart @@ -1,6 +1,6 @@ -import 'package:batucadapp/core/providers/prefs_provider.dart'; +import 'package:batucadapp/core/providers/prefs.dart'; import 'package:batucadapp/localization/language.dart'; -import 'package:batucadapp/localization/language_app_provider.dart'; +import 'package:batucadapp/localization/language_app_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -15,10 +15,11 @@ ProviderContainer makeProviderContainer( overrides: [ if (mockSharedPreferences != null) prefsProvider.overrideWith((_) => mockSharedPreferences), - languageAppProvider.overrideWith(LanguageApp.new), + languageAppControllerProvider.overrideWith(LanguageAppController.new), ], ); addTearDown(container.dispose); + return container; } @@ -28,7 +29,8 @@ void main() { group('Build Language App Provider', () { test('Return default when SharedPreferences doesnt have a value', () async { final container = makeProviderContainer(MockSharedPreferences()); - final language = await container.read(languageAppProvider.future); + final language = + await container.read(languageAppControllerProvider.future); expect( language.languageCode, TestWidgetsFlutterBinding @@ -41,7 +43,8 @@ void main() { TestWidgetsFlutterBinding.instance.platformDispatcher.localeTestValue = const Locale('und', ''); final container = makeProviderContainer(MockSharedPreferences()); - final language = await container.read(languageAppProvider.future); + final language = + await container.read(languageAppControllerProvider.future); expect( language.languageCode, 'en', @@ -54,7 +57,8 @@ void main() { }; final mockPrefs = MockSharedPreferences.setMockInitialValues(initialData); final container = makeProviderContainer(mockPrefs); - final language = await container.read(languageAppProvider.future); + final language = + await container.read(languageAppControllerProvider.future); expect(language, Language.pt); }); }); @@ -67,10 +71,11 @@ void main() { final mock = MockSharedPreferences.setMockInitialValues(initialData); when(() => mock.setString('locale', any())).thenAnswer((_) async => true); final container = makeProviderContainer(mock); - final languageApp = container.read(languageAppProvider.notifier); + final languageApp = + container.read(languageAppControllerProvider.notifier); await languageApp.setLanguage(Language.es); await expectLater( - container.read(languageAppProvider.future), + container.read(languageAppControllerProvider.future), completion(Language.es), ); }); @@ -82,10 +87,11 @@ void main() { final mock = MockSharedPreferences.setMockInitialValues(initialData); when(() => mock.remove('locale')).thenAnswer((_) async => true); final container = makeProviderContainer(mock); - final languageApp = container.read(languageAppProvider.notifier); + final languageApp = + container.read(languageAppControllerProvider.notifier); await languageApp.setLanguage(Language.es); await expectLater( - container.read(languageAppProvider.future), + container.read(languageAppControllerProvider.future), completion(Language.es), ); }); From 9f0e6e8fd5d90a2e972f1642afdcc9f3044754d3 Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Mon, 22 Apr 2024 01:23:20 -0400 Subject: [PATCH 5/9] Remove unused parameters for underscore --- .../app_animated_linear_gradient.dart | 7 +++++-- lib/common_widgets/cupertino_sheet_route.dart | 4 ++-- lib/core/providers/initialization.dart | 2 +- .../home/widgets/settings_modal_sheet.dart | 2 +- .../details/instrument_details_page.dart | 2 +- lib/features/parades/parades_tab_controller.dart | 2 +- lib/features/parades/parades_tab_page.dart | 2 +- lib/features/parades/widgets/parade_item.dart | 2 +- .../schools/details/school_details_page.dart | 8 ++++---- lib/features/schools/schools_tab_controller.dart | 2 +- lib/features/schools/widgets/school_card.dart | 4 ++-- .../schools/widgets/school_filter_chips.dart | 8 +++----- lib/features/schools/widgets/schools_tab_body.dart | 6 +++--- .../schools/widgets/schools_tab_navbar.dart | 4 ++-- lib/main.dart | 2 +- lib/routing/app_router.dart | 14 +++++++------- ...or_handler.dart => register_error_handler.dart} | 6 ++++-- pubspec.yaml | 1 - 18 files changed, 40 insertions(+), 38 deletions(-) rename lib/utils/{app_error_handler.dart => register_error_handler.dart} (85%) diff --git a/lib/common_widgets/app_animated_linear_gradient.dart b/lib/common_widgets/app_animated_linear_gradient.dart index 1745bee..e264832 100644 --- a/lib/common_widgets/app_animated_linear_gradient.dart +++ b/lib/common_widgets/app_animated_linear_gradient.dart @@ -36,7 +36,7 @@ class _AppAnimatedLinearGradientState extends State Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, - builder: (context, child) { + builder: (_, __) { return CustomPaint( painter: LinearGradientPainter( colors: widget.colors, @@ -61,6 +61,9 @@ class LinearGradientPainter extends CustomPainter { final Alignment begin; final Alignment end; + // Double the width to allow for a continuous flow + static const widthMultiplier = 2; + LinearGradientPainter({ required this.colors, required this.percent, @@ -81,7 +84,7 @@ class LinearGradientPainter extends CustomPainter { final shaderRect = Rect.fromLTWH( -size.width * percent, // Shift the left boundary of the gradient 0, - size.width * 2, // Double the width to allow for a continuous flow + size.width * widthMultiplier, size.height, ); diff --git a/lib/common_widgets/cupertino_sheet_route.dart b/lib/common_widgets/cupertino_sheet_route.dart index 7213e76..a79451d 100644 --- a/lib/common_widgets/cupertino_sheet_route.dart +++ b/lib/common_widgets/cupertino_sheet_route.dart @@ -124,7 +124,7 @@ class CupertinoSheetRoute extends SheetRoute { super.draggable = true, super.fit, }) : super( - builder: (BuildContext context) { + builder: (_) { return _CupertinoSheetDecorationBuilder( backgroundColor: backgroundColor, topRadius: _kCupertinoSheetTopRadius, @@ -183,7 +183,7 @@ class CupertinoSheetRoute extends SheetRoute { data: CupertinoUserInterfaceLevelData.elevated, child: child, ), - builder: (BuildContext context, Widget? child) { + builder: (_, Widget? child) { final progress = secondaryAnimation.value; final scale = 1 - progress / 10; final distanceWithScale = diff --git a/lib/core/providers/initialization.dart b/lib/core/providers/initialization.dart index c3291d3..b178d57 100644 --- a/lib/core/providers/initialization.dart +++ b/lib/core/providers/initialization.dart @@ -2,9 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../utils/app_error_handler.dart'; import '../../utils/app_loggers.dart'; import '../../utils/immutable_list.dart'; +import '../../utils/register_error_handler.dart'; import 'prefs.dart'; part 'initialization.g.dart'; diff --git a/lib/features/home/widgets/settings_modal_sheet.dart b/lib/features/home/widgets/settings_modal_sheet.dart index 8024781..2365ec0 100644 --- a/lib/features/home/widgets/settings_modal_sheet.dart +++ b/lib/features/home/widgets/settings_modal_sheet.dart @@ -19,7 +19,7 @@ void showSettingModalSheet( useRootNavigator: true, showDragHandle: false, isScrollControlled: true, - builder: (context) => SettingsModalSheet(showAsDialog: showAsDialog), + builder: (_) => SettingsModalSheet(showAsDialog: showAsDialog), ); } diff --git a/lib/features/instruments/details/instrument_details_page.dart b/lib/features/instruments/details/instrument_details_page.dart index 1dfd4a6..f8b8708 100644 --- a/lib/features/instruments/details/instrument_details_page.dart +++ b/lib/features/instruments/details/instrument_details_page.dart @@ -43,7 +43,7 @@ class _InstrumentDetailsPageState extends ConsumerState { child: Scaffold( body: NestedScrollView( controller: _controller, - headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + headerSliverBuilder: (BuildContext context, _) { return [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor( diff --git a/lib/features/parades/parades_tab_controller.dart b/lib/features/parades/parades_tab_controller.dart index 918fa6f..cad89a7 100644 --- a/lib/features/parades/parades_tab_controller.dart +++ b/lib/features/parades/parades_tab_controller.dart @@ -70,7 +70,7 @@ class ParadesTabReachedLimit extends _$ParadesTabReachedLimit { } } -final currentParadeProvider = Provider((ref) { +final currentParadeProvider = Provider((_) { throw UnimplementedError(); }); diff --git a/lib/features/parades/parades_tab_page.dart b/lib/features/parades/parades_tab_page.dart index bbfcbba..16cc975 100644 --- a/lib/features/parades/parades_tab_page.dart +++ b/lib/features/parades/parades_tab_page.dart @@ -73,7 +73,7 @@ class _ParadesTabPageState extends ConsumerState { child: SuperSliverList.builder( itemCount: value.length, listController: _listController, - itemBuilder: (context, index) { + itemBuilder: (_, index) { final parade = value[index]; return ProviderScope( diff --git a/lib/features/parades/widgets/parade_item.dart b/lib/features/parades/widgets/parade_item.dart index 8c284d3..fe5f349 100644 --- a/lib/features/parades/widgets/parade_item.dart +++ b/lib/features/parades/widgets/parade_item.dart @@ -101,7 +101,7 @@ class ParadeItemContent extends StatelessWidget { child: AppFadeInImage( parade.school.imageUrl, fadeInDuration: const Duration(milliseconds: 300), - imageErrorBuilder: (context, error, stackTrace) { + imageErrorBuilder: (_, __, ___) { return const SizedBox(); }, ), diff --git a/lib/features/schools/details/school_details_page.dart b/lib/features/schools/details/school_details_page.dart index b06b8f9..3f50e53 100644 --- a/lib/features/schools/details/school_details_page.dart +++ b/lib/features/schools/details/school_details_page.dart @@ -69,7 +69,7 @@ class _SchoolDetailsPageState extends ConsumerState { child: Column( children: [ LayoutBuilder( - builder: (context, constraints) => ConstrainedBox( + builder: (_, constraints) => ConstrainedBox( constraints: BoxConstraints( maxHeight: ((constraints.maxWidth - 20) / 3) * 2, ), @@ -83,7 +83,7 @@ class _SchoolDetailsPageState extends ConsumerState { itemCount: imageCount, onPageChanged: (value) => currentImage.value = value, - itemBuilder: (context, index) { + itemBuilder: (_, __) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 12, @@ -103,7 +103,7 @@ class _SchoolDetailsPageState extends ConsumerState { ), ValueListenableBuilder( valueListenable: currentImage, - builder: (context, index, child) { + builder: (_, index, __) { return AppPageIndicator( pageCount: imageCount, currentPage: index, @@ -117,7 +117,7 @@ class _SchoolDetailsPageState extends ConsumerState { ), ValueListenableBuilder( valueListenable: showOriginal, - builder: (context, value, child) { + builder: (_, value, __) { return SchoolDetailsText( school: school, showOriginal: value, diff --git a/lib/features/schools/schools_tab_controller.dart b/lib/features/schools/schools_tab_controller.dart index e92e78f..1c98217 100644 --- a/lib/features/schools/schools_tab_controller.dart +++ b/lib/features/schools/schools_tab_controller.dart @@ -199,4 +199,4 @@ class SchoolReachedMax extends _$SchoolReachedMax { /// This ensures that when we add/remove/edit schools, only what the /// impacted widgets rebuilds, instead of the entire list of items. final currentSchoolProvider = - Provider((ref) => throw UnimplementedError()); + Provider((_) => throw UnimplementedError()); diff --git a/lib/features/schools/widgets/school_card.dart b/lib/features/schools/widgets/school_card.dart index 7de31e4..0ad0457 100644 --- a/lib/features/schools/widgets/school_card.dart +++ b/lib/features/schools/widgets/school_card.dart @@ -81,7 +81,7 @@ class _SchoolCardState extends ConsumerState { ), ValueListenableBuilder( valueListenable: showOriginal, - builder: (context, value, child) { + builder: (_, value, __) { return SchoolInfoCard( school: school, showOriginal: value, @@ -173,7 +173,7 @@ class SchoolInfoCard extends StatelessWidget { ), Flexible( child: Consumer( - builder: (context, ref, child) { + builder: (context, ref, _) { final sort = ref.watch(selectedSchoolSortProvider); return Padding( diff --git a/lib/features/schools/widgets/school_filter_chips.dart b/lib/features/schools/widgets/school_filter_chips.dart index 0df5038..f7a813e 100644 --- a/lib/features/schools/widgets/school_filter_chips.dart +++ b/lib/features/schools/widgets/school_filter_chips.dart @@ -44,11 +44,9 @@ class SchoolFilterChips extends ConsumerWidget { selected: ref.watch(showOnlyFavoriteSchoolsProvider), label: Text(context.loc.schoolFavorites), selectedColor: context.colorScheme.primaryContainer, - onSelected: (value) { - ref - .read(showOnlyFavoriteSchoolsProvider.notifier) - .toggleShowFavorites(); - }, + onSelected: (_) => ref + .read(showOnlyFavoriteSchoolsProvider.notifier) + .toggleShowFavorites(), ), ), Padding( diff --git a/lib/features/schools/widgets/schools_tab_body.dart b/lib/features/schools/widgets/schools_tab_body.dart index 17bc882..868d028 100644 --- a/lib/features/schools/widgets/schools_tab_body.dart +++ b/lib/features/schools/widgets/schools_tab_body.dart @@ -24,8 +24,8 @@ class SchoolsTabBody extends ConsumerWidget { sliver: AppAsyncSliverWidget( asyncValue: ref.watch(schoolsTabControllerProvider), onErrorRetry: () => ref.invalidate(schoolsTabControllerProvider), - child: (value) => Consumer( - builder: (context, ref, child) { + child: (_) => Consumer( + builder: (_, ref, __) { final schools = ref.watch(filteredSchoolsProvider); return SliverCrossAxisConstrained( @@ -63,7 +63,7 @@ class SliverSchoolsList extends StatelessWidget { return SliverDynamicHeightGridView( itemCount: schools.length, crossAxisCount: context.screenSize.defaultCrossAxisCount, - builder: (context, index) { + builder: (_, index) { final school = schools[index]; return AppAnimationWrapper( diff --git a/lib/features/schools/widgets/schools_tab_navbar.dart b/lib/features/schools/widgets/schools_tab_navbar.dart index 3fd43e2..9a16d40 100644 --- a/lib/features/schools/widgets/schools_tab_navbar.dart +++ b/lib/features/schools/widgets/schools_tab_navbar.dart @@ -22,7 +22,7 @@ class SchoolsTabNavBar extends StatelessWidget { largeTitle: context.loc.schoolsTitle, leading: PullDownButton( // menuOffset: context.screenSize.currentRailWidth, - itemBuilder: (context) => [ + itemBuilder: (_) => [ // TODO(hectorAguero): Should get this from the Data PullDownMenuItem.selectable( title: '🇧🇷 Rio de Janeiro'.hardcoded, @@ -32,7 +32,7 @@ class SchoolsTabNavBar extends StatelessWidget { }, ), ], - buttonBuilder: (context, showMenu) => AppCupertinoButton( + buttonBuilder: (_, showMenu) => AppCupertinoButton( onPressed: showMenu, child: const Icon(CupertinoIcons.ellipsis_circle), ), diff --git a/lib/main.dart b/lib/main.dart index 4ce1459..34c8221 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,7 +56,7 @@ class MainApp extends ConsumerWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - builder: (context, child) => MediaQuery.withClampedTextScaling( + builder: (_, child) => MediaQuery.withClampedTextScaling( maxScaleFactor: 2, child: child ?? const SizedBox.shrink(), ), diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 33e306c..0a339a9 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -29,14 +29,14 @@ class AppRouter extends _$AppRouter { return GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: HomeTab.instruments.path, - errorPageBuilder: (context, state) => const NoTransitionPage( + errorPageBuilder: (_, __) => const NoTransitionPage( child: Scaffold(body: Text('404 - Not Found')), ), - redirect: (context, state) => _redirect(state, controllers), + redirect: (_, state) => _redirect(state, controllers), routes: [ GoRoute( path: Initialization.path, - pageBuilder: (context, state) => NoTransitionPage( + pageBuilder: (_, __) => NoTransitionPage( child: Initialization( // * Just a placeholder, route will be managed by GoRouter onLoaded: (_) => const SizedBox.shrink(), @@ -58,7 +58,7 @@ class AppRouter extends _$AppRouter { routes: [ GoRoute( path: InstrumentDetailsPage.path, - onExit: (context) { + onExit: (_) { Future.microtask( () => ref .read(homePageControllerProvider.notifier) @@ -90,7 +90,7 @@ class AppRouter extends _$AppRouter { routes: [ GoRoute( path: SchoolsTabPage.path, - builder: (context, state) { + builder: (_, __) { return PrimaryScrollController( controller: controllers[SchoolsTabPage.tab.name]!, child: const SchoolsTabPage(), @@ -99,14 +99,14 @@ class AppRouter extends _$AppRouter { routes: [ GoRoute( path: SchoolDetailsPage.path, - pageBuilder: (context, state) { + pageBuilder: (_, state) { return AppCupertinoSheetPage( child: SchoolDetailsPage( id: int.parse(state.pathParameters['id']!), ), ); }, - onExit: (context) { + onExit: (_) { Future.microtask( () => ref .read(homePageControllerProvider.notifier) diff --git a/lib/utils/app_error_handler.dart b/lib/utils/register_error_handler.dart similarity index 85% rename from lib/utils/app_error_handler.dart rename to lib/utils/register_error_handler.dart index 4574fee..abd6c75 100644 --- a/lib/utils/app_error_handler.dart +++ b/lib/utils/register_error_handler.dart @@ -2,15 +2,17 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'app_loggers.dart'; + void registerErrorHandlers() { // * Show some error UI if any uncaught exception happens FlutterError.onError = (FlutterErrorDetails details) { FlutterError.presentError(details); - debugPrint(details.toString()); + logViews.severe(details.toString()); }; // * Handle errors from the underlying platform/OS PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { - debugPrint(error.toString()); + logViews.severe(error.toString(), error, stack); return true; }; diff --git a/pubspec.yaml b/pubspec.yaml index 2b519fa..3bf79dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,6 @@ dev_dependencies: riverpod_lint: ^3.0.0-dev.4 solid_lints: ^0.1.2 very_good_analysis: ^5.1.0 - flutter: uses-material-design: true From e26fc323b834e4bb81a3ccaa8141f8e3f8ae9b31 Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Mon, 22 Apr 2024 10:05:58 -0400 Subject: [PATCH 6/9] Move some constants to own file --- lib/constants.dart | 3 ++ lib/features/parades/parades_tab_page.dart | 2 +- lib/features/schools/schools_tab_page.dart | 2 +- lib/utils/debouncer.dart | 7 ++-- lib/utils/screen_size.dart | 40 ++++++++++++---------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/constants.dart b/lib/constants.dart index eeb608b..c72196e 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -4,5 +4,8 @@ final class Constants { static const connectTimeout = Duration(seconds: 2); static const receiveTimeout = Duration(seconds: 3); + /// Duration(milliseconds: 300) + static const debouncerDelay = Duration(milliseconds: 300); + Constants._(); } diff --git a/lib/features/parades/parades_tab_page.dart b/lib/features/parades/parades_tab_page.dart index 16cc975..759db41 100644 --- a/lib/features/parades/parades_tab_page.dart +++ b/lib/features/parades/parades_tab_page.dart @@ -27,7 +27,7 @@ class ParadesTabPage extends ConsumerStatefulWidget { } class _ParadesTabPageState extends ConsumerState { - final _debouncer = Debouncer(defaultDelay); + final _debouncer = Debouncer(); final _listController = ListController(); late final ScrollController controller = PrimaryScrollController.of(context); diff --git a/lib/features/schools/schools_tab_page.dart b/lib/features/schools/schools_tab_page.dart index 838d6f4..45330a1 100644 --- a/lib/features/schools/schools_tab_page.dart +++ b/lib/features/schools/schools_tab_page.dart @@ -20,7 +20,7 @@ class SchoolsTabPage extends ConsumerStatefulWidget { } class _SchoolsTabState extends ConsumerState { - final _debouncer = Debouncer(defaultDelay); + final _debouncer = Debouncer(); late final ScrollController controller = PrimaryScrollController.of(context); @override diff --git a/lib/utils/debouncer.dart b/lib/utils/debouncer.dart index c3cc382..0839932 100644 --- a/lib/utils/debouncer.dart +++ b/lib/utils/debouncer.dart @@ -1,13 +1,14 @@ import 'dart:async'; -/// Duration(milliseconds: 300) -const defaultDelay = Duration(milliseconds: 300); +import '../constants.dart'; class Debouncer { final Duration delay; Timer? _timer; bool _isFirstCall = true; - Debouncer(this.delay); + + /// Debouncer constructor with default delay of 300 milliseconds + Debouncer([this.delay = Constants.debouncerDelay]); void run(void Function() action) { if (_isFirstCall) { diff --git a/lib/utils/screen_size.dart b/lib/utils/screen_size.dart index 519996f..7b038a5 100644 --- a/lib/utils/screen_size.dart +++ b/lib/utils/screen_size.dart @@ -1,16 +1,5 @@ import 'package:flutter/material.dart'; -/// For mobile in landscape for non scrollable content -const _smallHeight = 500.0; - -const _smallWidth = 600.0; -const _mediumWidth = 900.0; -const _largeWidth = 1200.0; - -const _smallAxisCount = 1; -const _mediumAxisCount = 2; -const _largeAxisCount = 3; - enum ScreenSize { xs, md, @@ -21,18 +10,18 @@ enum ScreenSize { bool get isLarge => this == ScreenSize.lg; double get value => switch (this) { - (ScreenSize.xs) => _smallWidth, - (ScreenSize.md) => _mediumWidth, - (ScreenSize.lg) => _largeWidth, + (ScreenSize.xs) => ScreenConstants.smallWidth, + (ScreenSize.md) => ScreenConstants.mediumWidth, + (ScreenSize.lg) => ScreenConstants.largeWidth, }; int get defaultCrossAxisCount => switch (this) { - (ScreenSize.xs) => _smallAxisCount, - (ScreenSize.md) => _mediumAxisCount, - (ScreenSize.lg) => _largeAxisCount, + (ScreenSize.xs) => ScreenConstants.smallAxisCount, + (ScreenSize.md) => ScreenConstants.mediumAxisCount, + (ScreenSize.lg) => ScreenConstants.largeAxisCount, }; - static double get smallHeight => _smallHeight; + static double get smallHeight => ScreenConstants.smallHeight; } extension MediaQueryExtension on BuildContext { @@ -47,3 +36,18 @@ extension MediaQueryExtension on BuildContext { bool get isSmallHeight => MediaQuery.sizeOf(this).height < ScreenSize.smallHeight; } + +final class ScreenConstants { + /// For mobile in landscape for non scrollable content + static const smallHeight = 500.0; + + static const smallWidth = 600.0; + static const mediumWidth = 900.0; + static const largeWidth = 1200.0; + + static const smallAxisCount = 1; + static const mediumAxisCount = 2; + static const largeAxisCount = 3; + + ScreenConstants._(); +} From bdda626de598136622783c3972d79f71f67b6a27 Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Wed, 1 May 2024 01:58:41 -0400 Subject: [PATCH 7/9] Add some tests, unable to link to code --- coverage/lcov.info | 1020 +++++++++++++---- lib/routing/app_router.dart | 4 +- pubspec.lock | 36 +- pubspec.yaml | 2 +- .../client_network_provider_test.dart | 26 +- test/create_container.dart | 20 + .../instruments/instruments_repo_test.dart | 74 ++ test/features/schools/schools_repo_test.dart | 12 - 8 files changed, 934 insertions(+), 260 deletions(-) create mode 100644 test/create_container.dart diff --git a/coverage/lcov.info b/coverage/lcov.info index 3f044f4..005c6ad 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,5 +1,5 @@ SF:lib/constants.dart -DA:7,0 +DA:10,0 LF:1 LH:0 end_of_record @@ -22,188 +22,869 @@ DA:41,0 DA:42,0 DA:43,0 DA:47,0 -DA:51,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:61,0 +DA:63,0 +DA:65,0 +DA:67,0 +DA:80,0 +DA:81,0 +DA:82,1 +DA:83,1 +DA:84,0 +DA:85,0 +DA:86,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:98,0 +DA:99,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +LF:49 +LH:2 +end_of_record +SF:lib/core/providers/client_network.g.dart +DA:9,0 +DA:13,2 +DA:14,1 +LF:3 +LH:2 +end_of_record +SF:lib/common_widgets/app_back_button.dart +DA:7,5 +DA:9,0 +DA:11,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:20,0 +DA:21,0 +LF:8 +LH:1 +end_of_record +SF:lib/core/extensions/is_ios_or_macos_platform_extension.dart +DA:3,0 +DA:4,0 +DA:5,0 +LF:3 +LH:0 +end_of_record +SF:lib/core/extensions/theme_of_context_extension.dart +DA:8,0 +DA:10,0 +DA:11,0 +DA:12,0 +DA:14,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:54,0 +DA:55,0 +LF:35 +LH:0 +end_of_record +SF:lib/common_widgets/app_cupertino_button.dart +DA:10,0 +DA:23,0 +DA:46,0 +DA:48,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:68,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:80,0 +DA:82,0 +DA:100,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +LF:30 +LH:0 +end_of_record +SF:lib/common_widgets/app_cupertino_sliver_navigation_bar.dart +DA:13,0 +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:46,0 +DA:47,0 +DA:51,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:58,0 +LF:20 +LH:0 +end_of_record +SF:lib/core/extensions/js_bottom_padding_extension.dart +DA:1,0 +DA:2,0 +DA:3,0 +DA:4,0 +LF:4 +LH:0 +end_of_record +SF:lib/features/home/widgets/settings_modal_sheet.dart +DA:12,0 +DA:16,0 +DA:22,0 +DA:28,0 +DA:33,0 +DA:35,0 +DA:37,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:80,5 +DA:82,0 +DA:84,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:92,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:104,0 +DA:106,0 +DA:108,0 +DA:109,0 +DA:111,0 +DA:112,0 +DA:115,0 +LF:40 +LH:1 +end_of_record +SF:lib/utils/screen_size.dart +DA:8,0 +DA:9,0 +DA:10,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:24,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:36,0 +DA:37,0 +DA:52,0 +LF:19 +LH:0 +end_of_record +SF:lib/common_widgets/app_fade_in_image.dart +DA:18,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:65,0 +DA:82,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:93,0 +DA:94,0 +DA:99,0 +DA:100,0 +DA:104,0 +DA:105,0 +LF:33 +LH:0 +end_of_record +SF:lib/core/extensions/app_localization_extension.dart +DA:6,0 +LF:1 +LH:0 +end_of_record +SF:lib/utils/app_loggers.dart +DA:4,0 +DA:5,3 +DA:6,0 +DA:7,0 +DA:8,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:25,0 +DA:28,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:49,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:58,0 +LF:29 +LH:1 +end_of_record +SF:lib/common_widgets/app_web_padding.dart +DA:11,0 +DA:21,0 +DA:30,0 +DA:41,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:52,0 +DA:56,0 +DA:61,0 +DA:77,0 +DA:86,0 +DA:90,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:102,0 +DA:107,0 +LF:27 +LH:0 +end_of_record +SF:lib/l10n/app_localizations.dart +DA:65,0 +DA:69,0 +DA:70,0 +DA:636,5 +DA:638,0 +DA:640,0 +DA:643,0 +DA:644,0 +DA:646,0 +DA:650,0 +DA:654,0 +DA:655,0 +DA:656,0 +DA:657,0 +DA:658,0 +DA:661,0 +LF:16 +LH:1 +end_of_record +SF:lib/core/extensions/text_lines_extension.dart +DA:6,0 +DA:7,0 +DA:8,0 +DA:10,0 +DA:13,0 +DA:15,0 +DA:18,0 +DA:24,0 +DA:26,0 +LF:9 +LH:0 +end_of_record +SF:lib/core/theme/app_theme.dart +DA:9,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:66,0 +DA:69,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:80,0 +DA:81,0 +DA:83,0 +DA:98,0 +DA:106,0 +DA:113,0 +DA:120,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:134,0 +DA:136,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:145,0 +LF:41 +LH:0 +end_of_record +SF:lib/localization/language.dart +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,1 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:47,1 +DA:48,2 +DA:50,4 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +LF:33 +LH:8 +end_of_record +SF:lib/localization/language_app_controller.dart +DA:10,1 +DA:12,4 +DA:13,1 +DA:14,4 +DA:16,1 +DA:17,3 +DA:18,1 +DA:22,1 +DA:25,4 +DA:26,1 +DA:27,1 +DA:29,2 +DA:31,2 +LF:13 +LH:13 +end_of_record +SF:lib/localization/language_app_controller.g.dart +DA:9,0 +DA:14,2 +DA:15,1 +LF:3 +LH:2 +end_of_record +SF:lib/core/providers/client_network/network_client_adapter.dart +DA:5,0 +DA:6,0 +DA:7,0 +DA:8,0 +DA:9,0 +DA:15,0 +DA:16,0 +LF:7 +LH:0 +end_of_record +SF:lib/core/providers/prefs.g.dart +DA:9,1 +DA:13,9 +LF:2 +LH:2 +end_of_record +SF:lib/core/providers/prefs.dart +DA:6,1 +DA:8,1 +LF:2 +LH:2 +end_of_record +SF:lib/core/theme/theme_mode_controller.dart +DA:11,1 +DA:14,4 +DA:15,1 +DA:18,1 +DA:19,1 +DA:23,0 +DA:29,1 +DA:30,1 +DA:31,1 +DA:32,4 +DA:33,1 +DA:34,1 +DA:35,3 +DA:36,1 +DA:37,1 +DA:38,1 +DA:39,4 +DA:40,1 +DA:41,1 +DA:42,1 +DA:43,4 +DA:44,1 +DA:45,1 +DA:49,1 +DA:50,4 +DA:52,1 +DA:53,1 +DA:54,1 +DA:55,1 +DA:56,1 +DA:57,1 +DA:58,1 +DA:59,1 +DA:60,1 +DA:67,1 +DA:70,5 +DA:73,3 +DA:79,1 +DA:80,4 +DA:81,2 +DA:82,1 +DA:83,2 +DA:85,1 +LF:43 +LH:42 +end_of_record +SF:lib/core/theme/theme_mode_controller.g.dart +DA:9,0 +DA:14,2 +DA:15,1 +DA:26,1 +DA:30,2 +DA:31,1 +LF:6 +LH:5 +end_of_record +SF:lib/features/home/widgets/settings_theme_section.dart +DA:9,5 +DA:11,0 +DA:13,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:43,0 +DA:45,0 +DA:47,0 +DA:49,0 +DA:54,0 +DA:55,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:68,0 +DA:69,0 +DA:71,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:77,0 +LF:39 +LH:1 +end_of_record +SF:lib/features/instruments/details/instrument_details_controller.g.dart +DA:9,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:20,0 +DA:21,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:49,5 +DA:55,0 +DA:58,0 +DA:62,0 +DA:66,0 +DA:69,0 +DA:74,0 +DA:79,0 +DA:80,0 +DA:85,0 +DA:86,0 +DA:91,0 +DA:99,0 +DA:103,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:128,0 +DA:136,0 +DA:140,0 +DA:144,0 +DA:145,0 +DA:149,0 +DA:151,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:160,0 +DA:165,0 +DA:167,0 +DA:170,0 +DA:173,0 +DA:176,0 +DA:179,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:184,0 +DA:185,0 +DA:186,0 +DA:190,0 +DA:192,0 +DA:195,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:213,0 +DA:215,0 +DA:216,0 +LF:60 +LH:1 +end_of_record +SF:lib/features/instruments/details/instrument_details_controller.dart +DA:9,0 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:16,0 +LF:6 +LH:0 +end_of_record +SF:lib/features/instruments/instrument.dart +DA:21,0 +LF:1 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_tab_controller.dart +DA:11,0 +DA:13,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +LF:7 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_tab_controller.g.dart +DA:9,0 +DA:14,0 +LF:2 +LH:0 +end_of_record +SF:lib/features/instruments/details/instrument_details_page.dart +DA:19,0 +DA:25,0 +DA:27,0 +DA:35,0 +DA:37,0 +DA:38,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 DA:52,0 -DA:53,0 DA:54,0 -DA:61,0 +DA:56,0 DA:63,0 +DA:64,0 DA:65,0 +DA:66,0 DA:67,0 -DA:80,0 -DA:81,0 -DA:82,1 -DA:83,1 +DA:68,0 +DA:72,0 +DA:74,0 +DA:76,0 +DA:77,0 DA:84,0 -DA:85,0 DA:86,0 +DA:87,0 +DA:88,0 +DA:92,0 DA:93,0 -DA:95,0 -DA:96,0 -DA:98,0 -DA:99,0 DA:101,0 DA:102,0 -DA:104,0 -DA:105,0 -DA:106,0 -DA:107,0 -DA:108,0 -DA:109,0 +DA:103,0 DA:110,0 -DA:111,0 DA:112,0 -LF:49 -LH:2 -end_of_record -SF:lib/core/providers/client_network.g.dart -DA:9,0 -DA:13,2 -DA:14,1 -LF:3 -LH:2 -end_of_record -SF:lib/core/extensions/app_localization_extension.dart -DA:6,0 -LF:1 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:148,0 +DA:153,0 +LF:54 LH:0 end_of_record -SF:lib/l10n/app_localizations.dart -DA:65,0 -DA:69,0 -DA:70,0 -DA:636,4 -DA:638,0 -DA:640,0 -DA:643,0 -DA:644,0 -DA:646,0 -DA:650,0 -DA:654,0 -DA:655,0 -DA:656,0 -DA:657,0 -DA:658,0 -DA:661,0 -LF:16 -LH:1 -end_of_record -SF:lib/localization/language.dart +SF:lib/features/instruments/details/widgets/instrument_details_summary.dart +DA:5,0 DA:12,0 -DA:13,0 DA:14,0 DA:15,0 DA:16,0 +DA:17,0 DA:19,0 DA:20,0 -DA:21,0 DA:22,0 DA:23,0 +DA:24,0 +LF:11 +LH:0 +end_of_record +SF:lib/features/instruments/details/widgets/instrument_header_images.dart +DA:12,0 +DA:22,0 +DA:24,0 DA:26,0 -DA:27,0 DA:28,0 -DA:29,0 DA:30,0 -DA:33,1 -DA:34,1 -DA:35,1 -DA:36,1 -DA:37,1 -DA:40,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 DA:41,0 DA:42,0 -DA:43,0 DA:44,0 -DA:47,1 -DA:48,2 -DA:50,4 -DA:55,0 -DA:56,0 +DA:45,0 +DA:46,0 +DA:53,0 DA:57,0 DA:58,0 DA:59,0 -LF:33 -LH:8 -end_of_record -SF:lib/localization/language_app_controller.dart -DA:10,1 -DA:12,4 -DA:13,1 -DA:14,4 -DA:16,1 -DA:17,3 -DA:18,1 -DA:22,1 -DA:25,4 -DA:26,1 -DA:27,1 -DA:29,2 -DA:31,2 -LF:13 -LH:13 -end_of_record -SF:lib/localization/language_app_controller.g.dart -DA:9,0 -DA:14,2 -DA:15,1 -LF:3 -LH:2 +DA:61,0 +DA:62,0 +DA:74,0 +DA:75,0 +DA:77,0 +DA:79,0 +DA:80,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:93,0 +DA:97,0 +DA:99,0 +DA:100,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:141,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:154,0 +LF:55 +LH:0 end_of_record -SF:lib/utils/app_loggers.dart -DA:4,0 -DA:5,3 -DA:6,0 -DA:7,0 +SF:lib/utils/immutable_list.dart DA:8,0 +DA:10,0 DA:12,0 DA:14,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:22,0 -DA:23,0 -DA:25,0 -DA:28,0 -DA:33,0 +DA:15,0 +LF:5 +LH:0 +end_of_record +SF:lib/features/instruments/instruments_repo.dart +DA:10,0 +DA:12,0 +DA:24,0 +DA:26,0 +DA:29,0 +DA:31,0 +DA:32,0 DA:34,0 -DA:38,0 +DA:35,0 +DA:36,0 DA:39,0 -DA:40,0 -DA:41,0 -DA:42,0 -DA:44,0 -DA:49,0 +DA:43,0 +DA:46,0 +DA:47,0 +DA:48,0 DA:51,0 -DA:52,0 DA:53,0 -DA:54,0 -DA:58,0 -LF:29 -LH:1 -end_of_record -SF:lib/core/providers/client_network/network_client_adapter.dart -DA:5,0 -DA:6,0 -DA:7,0 -DA:8,0 -DA:9,0 -DA:15,0 -DA:16,0 -LF:7 +LF:17 LH:0 end_of_record -SF:lib/core/providers/prefs.g.dart -DA:9,1 -DA:13,9 -LF:2 -LH:2 -end_of_record -SF:lib/core/providers/prefs.dart -DA:6,1 -DA:8,1 +SF:lib/features/instruments/instruments_repo.g.dart +DA:9,0 +DA:13,3 LF:2 -LH:2 +LH:1 end_of_record SF:lib/l10n/app_localizations_en.dart DA:5,0 @@ -581,60 +1262,3 @@ DA:271,0 LF:90 LH:0 end_of_record -SF:lib/core/theme/theme_mode_controller.dart -DA:11,1 -DA:14,4 -DA:15,1 -DA:18,1 -DA:19,1 -DA:23,0 -DA:29,1 -DA:30,1 -DA:31,1 -DA:32,4 -DA:33,1 -DA:34,1 -DA:35,3 -DA:36,1 -DA:37,1 -DA:38,1 -DA:39,4 -DA:40,1 -DA:41,1 -DA:42,1 -DA:43,4 -DA:44,1 -DA:45,1 -DA:49,1 -DA:50,4 -DA:52,1 -DA:53,1 -DA:54,1 -DA:55,1 -DA:56,1 -DA:57,1 -DA:58,1 -DA:59,1 -DA:60,1 -DA:67,1 -DA:70,5 -DA:73,3 -DA:79,1 -DA:80,4 -DA:81,2 -DA:82,1 -DA:83,2 -DA:85,1 -LF:43 -LH:42 -end_of_record -SF:lib/core/theme/theme_mode_controller.g.dart -DA:9,0 -DA:14,2 -DA:15,1 -DA:26,1 -DA:30,2 -DA:31,1 -LF:6 -LH:5 -end_of_record diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 0a339a9..7f68aaa 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -58,7 +58,7 @@ class AppRouter extends _$AppRouter { routes: [ GoRoute( path: InstrumentDetailsPage.path, - onExit: (_) { + onExit: (_, __) { Future.microtask( () => ref .read(homePageControllerProvider.notifier) @@ -106,7 +106,7 @@ class AppRouter extends _$AppRouter { ), ); }, - onExit: (_) { + onExit: (_, __) { Future.microtask( () => ref .read(homePageControllerProvider.notifier) diff --git a/pubspec.lock b/pubspec.lock index 2f7e951..69c3c7d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.5.0" args: dependency: transitive description: @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: easy_image_viewer - sha256: "750bb85e0a34504557d378a616110540caeec2324490fc040709589219e75834" + sha256: eca828c492d580f9bd775495b281cf6481d34109b73010f1e9a3db87c4d2e5f7 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.5.0" extended_image: dependency: "direct main" description: @@ -489,10 +489,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "771c8feb40ad0ef639973d7ecf1b43d55ffcedb2207fd43fab030f5639e40446" + sha256: "9e0f7d1a3e7dc5010903e330fbc5497872c4c3cf6626381d69083cc1d5113c1e" url: "https://pub.dev" source: hosted - version: "13.2.4" + version: "14.0.2" graphs: dependency: transitive description: @@ -617,10 +617,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -797,14 +797,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" - url: "https://pub.dev" - source: hosted - version: "3.8.0" pool: dependency: transitive description: @@ -1142,10 +1134,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.0" + version: "14.2.1" watcher: dependency: transitive description: @@ -1166,10 +1158,10 @@ packages: dependency: transitive description: name: web_socket - sha256: "7c57c6d0eb006e64d962d5c832bbe2ae9adbd887829944065f7467cbe13f6d8f" + sha256: bfe704c186c6e32a46f6607f94d079cd0b747b9a489fceeecc93cd3adb98edd5 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" web_socket_channel: dependency: transitive description: @@ -1182,10 +1174,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3bf79dd..52f52d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: sdk: flutter flutter_riverpod: ^3.0.0-dev.3 flutter_staggered_grid_view: ^0.7.0 - go_router: ^13.2.1 + go_router: ^14.0.2 intl: ^0.19.0 logging: ^1.2.0 native_dio_adapter: ^1.3.0 diff --git a/test/core/providers/client_network_provider_test.dart b/test/core/providers/client_network_provider_test.dart index 976f6ba..85b0467 100644 --- a/test/core/providers/client_network_provider_test.dart +++ b/test/core/providers/client_network_provider_test.dart @@ -9,13 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:mocktail/mocktail.dart'; -class MockBaseOptions with Mock implements BaseOptions {} - -class MockDio with Mock implements Dio {} - -class MockCacheInterceptor with Mock implements Interceptor {} - -class MockLogInterceptor with Mock implements Interceptor {} +import '../../create_container.dart'; class MockClientNetwork extends AsyncNotifier with Mock @@ -64,21 +58,3 @@ void main() { }); }); } - -ProviderContainer createContainer({ - ProviderContainer? parent, - List overrides = const [], - List? observers, -}) { - // Create a ProviderContainer, and optionally allow specifying parameters. - final container = ProviderContainer( - parent: parent, - overrides: overrides, - observers: observers, - ); - - // When the test ends, dispose the container. - addTearDown(container.dispose); - - return container; -} diff --git a/test/create_container.dart b/test/create_container.dart new file mode 100644 index 0000000..c3fd97f --- /dev/null +++ b/test/create_container.dart @@ -0,0 +1,20 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // Create a ProviderContainer, and optionally allow specifying parameters. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // When the test ends, dispose the container. + addTearDown(container.dispose); + + return container; +} diff --git a/test/features/instruments/instruments_repo_test.dart b/test/features/instruments/instruments_repo_test.dart index 8b13789..53575ca 100644 --- a/test/features/instruments/instruments_repo_test.dart +++ b/test/features/instruments/instruments_repo_test.dart @@ -1 +1,75 @@ +import 'package:batucadapp/features/instruments/instrument.dart'; +import 'package:batucadapp/features/instruments/instruments_repo.dart'; +import 'package:batucadapp/utils/immutable_list.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import '../../create_container.dart'; + +class InstrumentsRepoMock with Mock implements InstrumentsRepoImpls {} + +void main() { + group('Instruments Repository', () { + test( + ''' + When InstrumentsRepo is called + Should return a instance of InstrumentsRepoImpls + ''', + () { + final mock = InstrumentsRepoMock(); + final container = createContainer( + overrides: [instrumentsRepoProvider.overrideWith((_) => mock)], + ); + + expect( + container.read(instrumentsRepoProvider), + isA(), + ); + }, + ); + + test( + ''' + When getInstruments is called + Should return a immutable list of instruments + ''', + () { + final mock = InstrumentsRepoMock(); + when(mock.getInstruments).thenAnswer( + (_) async => ImmutableList(const []), + ); + final container = createContainer( + overrides: [instrumentsRepoProvider.overrideWith((_) => mock)], + ); + + expectLater( + container.read(instrumentsRepoProvider).getInstruments(), + completion(isA>()), + ); + verify(mock.getInstruments).called(1); + }, + ); + + test( + ''' + When getInstruments is called + Should return a immutable list of instruments + ''', + () async { + final mock = InstrumentsRepoMock(); + when(mock.getInstruments).thenThrow( + Exception("Error"), + ); + final container = createContainer( + overrides: [instrumentsRepoProvider.overrideWith((_) => mock)], + ); + + await expectLater( + () => container.read(instrumentsRepoProvider).getInstruments(), + throwsA(isA()), + ); + verify(mock.getInstruments).called(1); + }, + ); + }); +} diff --git a/test/features/schools/schools_repo_test.dart b/test/features/schools/schools_repo_test.dart index d802c27..8b13789 100644 --- a/test/features/schools/schools_repo_test.dart +++ b/test/features/schools/schools_repo_test.dart @@ -1,13 +1 @@ -// import 'package:batucadapp/features/schools/schools_repo.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import '../../create_container.dart'; - -// import 'package:mocktail/mocktail.dart'; -// import 'package:riverpod/riverpod.dart'; - -// class MockNetworkClient extends Mock implements NetworkClient {} - -// class MockSchoolsRepoRef extends Mock implements SchoolsRepoRef {} - -// void main() {} From 3bae9af3466e3558f6513655d995a84c812aba9d Mon Sep 17 00:00:00 2001 From: "hector.aguero.brisso" Date: Wed, 1 May 2024 06:41:56 +0000 Subject: [PATCH 8/9] Add some tests with Google IDX ide --- .idx/dev.nix | 41 +++++++++ pubspec.lock | 4 +- test/features/schools/schools_repo_test.dart | 94 ++++++++++++++++++++ 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 .idx/dev.nix diff --git a/.idx/dev.nix b/.idx/dev.nix new file mode 100644 index 0000000..7051cf9 --- /dev/null +++ b/.idx/dev.nix @@ -0,0 +1,41 @@ +{pkgs}: { + channel = "stable-23.11"; + packages = [ + pkgs.nodePackages.firebase-tools + pkgs.jdk17 + pkgs.unzip + ]; + idx.extensions = [ + + ]; + idx.previews = { + previews = { + web = { + command = [ + "flutter" + "run" + "--machine" + "-d" + "web-server" + "--web-hostname" + "0.0.0.0" + "--web-port" + "$PORT" + ]; + manager = "flutter"; + }; + android = { + command = [ + "flutter" + "run" + "--machine" + "-d" + "android" + "-d" + "emulator-5554" + ]; + manager = "flutter"; + }; + }; + }; +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 69c3c7d..634b16d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1134,10 +1134,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.0" watcher: dependency: transitive description: diff --git a/test/features/schools/schools_repo_test.dart b/test/features/schools/schools_repo_test.dart index 8b13789..cacca20 100644 --- a/test/features/schools/schools_repo_test.dart +++ b/test/features/schools/schools_repo_test.dart @@ -1 +1,95 @@ +import 'package:batucadapp/features/schools/school.dart'; +import 'package:batucadapp/features/schools/schools_repo.dart'; +import 'package:batucadapp/utils/immutable_list.dart'; +import 'package:dio/dio.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import '../../create_container.dart'; + +class SchoolsRepoMock with Mock implements SchoolsRepoImpls {} + +void main() { + group('Schools Repository', () { + test( + ''' + When SchoolsRepo is called + Should return a instance of SchoolsRepoImpls + ''', + () { + final mock = SchoolsRepoMock(); + final container = createContainer( + overrides: [schoolsRepoProvider.overrideWith((_) => mock)], + ); + + expect( + container.read(schoolsRepoProvider), + isA(), + ); + }, + ); + + test(''' + When SchoolsRepo.getSchools is called + Should return a list of Schools + ''', () async { + const page = 1; + const pageSize = 10; + final mock = SchoolsRepoMock(); + when( + () => mock.getSchools( + page: any(named: 'page'), + pageSize: any(named: 'pageSize'), + sort: any(named: 'sort'), + search: any(named: 'search'), + ), + ).thenAnswer((_) async => [].lock); + + final container = createContainer( + overrides: [schoolsRepoProvider.overrideWith((_) => mock)], + ); + + expect( + await container.read(schoolsRepoProvider).getSchools( + page: page, + pageSize: pageSize, + sort: 'name', + search: '', + ), + isA>(), + ); + }); + + test(''' + When SchoolsRepo.getSchools is called + Should throw an error + ''', () async { + const page = 1; + const pageSize = 10; + final mock = SchoolsRepoMock(); + when( + () => mock.getSchools( + page: any(named: 'page'), + pageSize: any(named: 'pageSize'), + sort: any(named: 'sort'), + search: any(named: 'search'), + ), + ).thenThrow(DioException(requestOptions: RequestOptions())); + + final container = createContainer( + overrides: [schoolsRepoProvider.overrideWith((_) => mock)], + ); + + expect( + () => container.read(schoolsRepoProvider).getSchools( + page: page, + pageSize: pageSize, + sort: 'name', + search: '', + ), + throwsA(isA()), + ); + }); + }); +} From 4425322de0fc3dbaa6ae0163da97203ebf28f1ac Mon Sep 17 00:00:00 2001 From: hectorAguero Date: Wed, 1 May 2024 13:19:56 -0400 Subject: [PATCH 9/9] Update dependencies and remove unused code --- .github/workflows/test.yml | 10 +- analysis_options.yaml | 1 - coverage/lcov.info | 101 +++++++++++++++++- lib/main.dart | 2 - pubspec.lock | 14 +-- pubspec.yaml | 3 +- .../client_network_provider_test.dart | 21 +++- .../instruments/instruments_repo_test.dart | 2 +- test/features/schools/schools_repo_test.dart | 2 +- test/{create_container.dart => utils.dart} | 0 10 files changed, 129 insertions(+), 27 deletions(-) rename test/{create_container.dart => utils.dart} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a18dec..b9eeef8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,10 @@ jobs: drive: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2 - - run: flutter test \ No newline at end of file + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: beta + cache: true + + - run: flutter test \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index c4d544c..e19c4b6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,3 @@ - # include: package:very_good_analysis/analysis_options.yaml include: package:solid_lints/analysis_options.yaml diff --git a/coverage/lcov.info b/coverage/lcov.info index 005c6ad..90f9738 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -64,7 +64,7 @@ LF:3 LH:2 end_of_record SF:lib/common_widgets/app_back_button.dart -DA:7,5 +DA:7,6 DA:9,0 DA:11,0 DA:15,0 @@ -207,7 +207,7 @@ DA:53,0 DA:55,0 DA:56,0 DA:57,0 -DA:80,5 +DA:80,6 DA:82,0 DA:84,0 DA:86,0 @@ -364,7 +364,7 @@ SF:lib/l10n/app_localizations.dart DA:65,0 DA:69,0 DA:70,0 -DA:636,5 +DA:636,6 DA:638,0 DA:640,0 DA:643,0 @@ -580,7 +580,7 @@ LF:6 LH:5 end_of_record SF:lib/features/home/widgets/settings_theme_section.dart -DA:9,5 +DA:9,6 DA:11,0 DA:13,0 DA:14,0 @@ -633,7 +633,7 @@ DA:24,0 DA:26,0 DA:28,0 DA:29,0 -DA:49,5 +DA:49,6 DA:55,0 DA:58,0 DA:62,0 @@ -1262,3 +1262,94 @@ DA:271,0 LF:90 LH:0 end_of_record +SF:lib/features/schools/school.dart +DA:44,0 +DA:119,0 +DA:120,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:129,2 +DA:131,0 +DA:133,0 +DA:135,0 +DA:136,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:144,0 +LF:16 +LH:1 +end_of_record +SF:lib/features/schools/schools_repo.dart +DA:9,0 +DA:11,0 +DA:26,0 +DA:28,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +LF:16 +LH:0 +end_of_record +SF:lib/features/schools/schools_repo.g.dart +DA:9,0 +DA:13,3 +LF:2 +LH:1 +end_of_record +SF:lib/features/schools/school_color_hook.dart +DA:8,2 +DA:10,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:58,0 +DA:59,0 +LF:41 +LH:1 +end_of_record diff --git a/lib/main.dart b/lib/main.dart index 34c8221..1865cad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:country_picker/country_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -50,7 +49,6 @@ class MainApp extends ConsumerWidget { themeMode: themeMode, themeAnimationStyle: AnimationStyle.noAnimation, localizationsDelegates: const [ - CountryLocalizations.delegate, AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, diff --git a/pubspec.lock b/pubspec.lock index 634b16d..db3d582 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -193,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" - country_picker: - dependency: "direct main" - description: - name: country_picker - sha256: "98a7cddb9413293d9aea13829120686c0a37c8854ba20088dc9f66ad28b503eb" - url: "https://pub.dev" - source: hosted - version: "2.0.25" cronet_http: dependency: transitive description: @@ -1134,10 +1126,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.0" + version: "14.2.1" watcher: dependency: transitive description: @@ -1204,4 +1196,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.3 <4.0.0" - flutter: ">=3.22.0-0.1.pre" + flutter: ">=3.22.0-0.3.pre" diff --git a/pubspec.yaml b/pubspec.yaml index 52f52d2..d20bbd0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,10 +7,9 @@ version: 1.0.0+1 environment: sdk: '>=3.3.3 <4.0.0' - flutter: 3.22.0-0.1.pre + flutter: 3.22.0-0.3.pre dependencies: - country_picker: ^2.0.25 cupertino_icons: ^1.0.6 dart_mappable: ^4.2.2 dio: ^5.4.3 diff --git a/test/core/providers/client_network_provider_test.dart b/test/core/providers/client_network_provider_test.dart index 85b0467..02112cf 100644 --- a/test/core/providers/client_network_provider_test.dart +++ b/test/core/providers/client_network_provider_test.dart @@ -9,12 +9,17 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:mocktail/mocktail.dart'; -import '../../create_container.dart'; +import '../../utils.dart'; class MockClientNetwork extends AsyncNotifier with Mock implements ClientNetwork {} +// a generic Listener class, used to track a provider notifying its listeners +class Listener extends Mock { + void call(T? previous, T next); +} + void main() { const dioOkResponseCode = 200; group('ClientNetwork build', () { @@ -28,10 +33,20 @@ void main() { final dio = Dio(baseOptions); final mock = MockClientNetwork(); when(mock.build).thenReturn(dio); + final container = createContainer( overrides: [clientNetworkProvider.overrideWith(() => mock)], ); + // create a listener + final listener = Listener>(); + // listen to the provider and call [listener] whenever its value changes + container.listen( + clientNetworkProvider, + listener, + fireImmediately: true, + ); + final readDio = await container.read(clientNetworkProvider.future); DioAdapter(dio: readDio).onGet( Endpoint.basePath.path, @@ -40,6 +55,10 @@ void main() { final response = await readDio.get(Endpoint.basePath.path); + // verify + verify(() => listener(null, AsyncData(dio))); + // verify that the listener is no longer called + verifyNoMoreInteractions(listener); expect(response.data, 'OK'); expect( readDio.options, diff --git a/test/features/instruments/instruments_repo_test.dart b/test/features/instruments/instruments_repo_test.dart index 53575ca..6d1384d 100644 --- a/test/features/instruments/instruments_repo_test.dart +++ b/test/features/instruments/instruments_repo_test.dart @@ -4,7 +4,7 @@ import 'package:batucadapp/utils/immutable_list.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import '../../create_container.dart'; +import '../../utils.dart'; class InstrumentsRepoMock with Mock implements InstrumentsRepoImpls {} diff --git a/test/features/schools/schools_repo_test.dart b/test/features/schools/schools_repo_test.dart index cacca20..891eb80 100644 --- a/test/features/schools/schools_repo_test.dart +++ b/test/features/schools/schools_repo_test.dart @@ -6,7 +6,7 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import '../../create_container.dart'; +import '../../utils.dart'; class SchoolsRepoMock with Mock implements SchoolsRepoImpls {} diff --git a/test/create_container.dart b/test/utils.dart similarity index 100% rename from test/create_container.dart rename to test/utils.dart