diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index 9edffd0c3..25128bfb9 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -11,9 +11,9 @@ defaults: working-directory: ./app env: - JAVA_VERSION: 12.x + JAVA_VERSION: 17.x FLUTTER_CHANNEL: stable - FLUTTER_VERSION: 3.7.6 + FLUTTER_VERSION: 3.16.4 jobs: lint: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31dea12d6..b5da93f09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,18 +9,23 @@ Before you begin with setting up your local development environment, ensure that you have the following list of tools installed: - An editor, we **strongly recommend** [vscode-icon Visual Studio Code](https://code.visualstudio.com) - [git-icon Git](https://git-scm.com/downloads) - [docker-icon Docker](https://docs.docker.com/get-docker/) - [nodejs-icon NodeJS](https://nodejs.org) - [yarn-icon Yarn](https://yarnpkg.com/) diff --git a/app/.gitignore b/app/.gitignore index a80a374fb..4ff0d8334 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -30,6 +30,7 @@ .pub-cache/ .pub/ /build/ +devtools_options.yaml # Web related lib/generated_plugin_registrant.dart diff --git a/app/CONTRIBUTING.md b/app/CONTRIBUTING.md index 395707cfa..1c47088e6 100644 --- a/app/CONTRIBUTING.md +++ b/app/CONTRIBUTING.md @@ -11,8 +11,8 @@ Please also see the [contribution guide in the root folder](../CONTRIBUTING.md). - Run `dart pub get` to fetch all dart dependencies - Run `flutter pub get` to fetch all flutter dependencies and setup all generated code - - Run `flutter pub run build_runner build --delete-conflicting-outputs` or - `flutter pub run build_runner watch --delete-conflicting-outputs` to + - Run `dart run build_runner build --delete-conflicting-outputs` or + `dart run build_runner watch --delete-conflicting-outputs` to re-generate code upon file changes while developing You should now be able to run the app by opening the debug panel on the left and @@ -24,42 +24,71 @@ For (cleaning) generated code, you might want to add the following aliases to your shell configuration: ```bash -alias flutter-generate='flutter pub run build_runner build --delete-conflicting-outputs' +alias flutter-generate='dart run build_runner build --delete-conflicting-outputs' alias flutter-clean='find . -maxdepth 20 -type f \( -name "*.inject.summary" -o -name "*.inject.dart" -o -name "*.g.dart" \) -delete' ``` ## Architecture -The app consists of multiple so-called modules. Our main modules correspond to -the direct subfolders of `lib/`. +The app consists of multiple so-called modules. Our main modules (usually app +screens) correspond to the direct subfolders of `lib/`. -### Example Module +Common functions used by modules such as `models`, `widgets`, and `utilities` +are living in `common`. All such functions are exported from +`common/module.dart`. -Structure of `lib/my_module`: +The structure of an example module `lib/my_module` should look as follows: - `my_module` - - `module.dart`: - - exports everything that is required by other modules - - declares all routes as a const variable (`myModuleRoutes`) + - `module.dart` (see example below): + - exports everything that is required by other modules, i.e., page(s) and + possibly the cubit + - declares all routes as functions reeturning `AutoRoute` - may contain initialization code (`initMyModule()`) - - `cubit.dart`: contains `MyModuleCubit` and `MyModuleState`s - `widgets`: - `my_widget.dart`: contains `MyWidget` and helpers - `pages`: - - `my_first.dart`: contains `MyFirstPage` and helpers - - `my_complex`: create a folder for complex pages (e.g., tabbed ones) - - `page.dart`: contains `MyComplexPage` - - `tab_first.dart`: contains `FirstTab` and helpers - - `tab_second.dart`: contains `SecondTab` and helpers - - `utils.dart`: contains utilities used by multiple files in this page + - `my_module.dart`: contains `MyModulePage` and helpers + - `my_child_page.dart`: contains + - `my_complex_page`: create a folder for complex pages (e.g., tabbed ones); + might want to create an own module if getting too complex - `utils.dart`: contains utilities used throughout this module - - `submodule_one` - - `submodule_two` - -If a single file gets too complex for routes, the `Cubit`, a widget, a page, -etc., you can create a folder with the same name and split the original file -into different files. An example of that is `MyComplexPage` in the file tree -above. + - `cubit.dart`: contains `MyModuleCubit` and `MyModuleState`s (if needed) + +Example for `my_module/module.dart`; the page is used as a root page in the tab +router, which is why the empty router `MyModuleRootPage` and adding +`AutoRoute(path: '', page: MyModuleRoute.page)` to children is needed. + +```dart +import '../common/module.dart'; + +// For generated routes +export 'cubit.dart'; +export 'pages/my_module.dart'; +export 'pages/my_child_page.dart'; +export 'pages/my_complex_page/page.dart'; + +@RoutePage() +class MyModuleRootPage extends AutoRouter {} + +AutoRoute myChildRoute() => AutoRoute( + path: 'my_child', + page: MyChildRoute.page, +); +AutoRoute myComplexRoute() => AutoRoute( + path: 'my_complex', + page: MyComplexRoute.page, +); + +AutoRoute myModuleRoute({ required List children }) => AutoRoute( + path: 'my_module', + page: MyModuleRootRoute.page, + children: [ + AutoRoute(path: '', page: MyModuleRoute.page), + ...children, // includes myChildRoute() and priva + ], +); +``` ## Making app icons diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index e9a640652..f35701529 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -26,15 +26,17 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + namespace 'de.hpi.pharme' + + compileSdk 33 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { @@ -42,8 +44,7 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "de.hpi.pharme" + applicationId 'de.hpi.pharme' minSdkVersion 19 targetSdkVersion 33 multiDexEnabled true diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml index d1df7c5b4..f880684a6 100644 --- a/app/android/app/src/debug/AndroidManifest.xml +++ b/app/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index d07f3150a..beab9a926 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml index d1df7c5b4..f880684a6 100644 --- a/app/android/app/src/profile/AndroidManifest.xml +++ b/app/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/app/android/build.gradle b/app/android/build.gradle index 76374445e..aca8b4a20 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -24,6 +24,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/app/android/gradle.properties b/app/android/gradle.properties index 94adc3a3f..b9a9a2464 100644 --- a/app/android/gradle.properties +++ b/app/android/gradle.properties @@ -1,3 +1,6 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..5c6f89dba 100644 --- a/app/android/gradle/wrapper/gradle-wrapper.properties +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/app/generate_screenshots/app_test.dart b/app/generate_screenshots/app_test.dart index c33e5014b..cf1cddd59 100644 --- a/app/generate_screenshots/app_test.dart +++ b/app/generate_screenshots/app_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; +import 'package:app/app.dart'; import 'package:app/common/module.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -51,6 +52,8 @@ void main() { await takeScreenshot(tester, binding, 'login'); // login-redirect (not working; only taking screenshot of loading screen) + // could try to use cubit function to directly sign in which will only + // open the webview and close it again // await tester.tap(find.byType(FullWidthButton).first); // await Future.delayed(Duration(seconds: 3)); // wait for dialog // await takeScreenshot(tester, binding, 'login-redirect'); diff --git a/app/generate_screenshots/test_driver.dart b/app/generate_screenshots/test_driver.dart index 5dee08a09..d7bd66f77 100644 --- a/app/generate_screenshots/test_driver.dart +++ b/app/generate_screenshots/test_driver.dart @@ -7,7 +7,7 @@ import 'package:integration_test/integration_test_driver_extended.dart'; Future main() async { try { await integrationDriver( - onScreenshot: (screenshotName, screenshotBytes) async { + onScreenshot: (screenshotName, screenshotBytes, [_]) async { final image = await File( '../docs/screenshots/$screenshotName.png' diff --git a/app/integration_test/drugs_test.dart b/app/integration_test/drugs_test.dart index bc87a03bd..b3bbca4e9 100644 --- a/app/integration_test/drugs_test.dart +++ b/app/integration_test/drugs_test.dart @@ -1,8 +1,8 @@ // ignore_for_file: cast_nullable_to_non_nullable import 'package:app/common/module.dart'; -import 'package:app/common/pages/drug/widgets/module.dart'; -import 'package:app/search/module.dart'; +import 'package:app/drug/module.dart'; +import 'package:app/drug/widgets/annotation_cards/disclaimer.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -126,7 +126,7 @@ void main() { // test the right color of the card // ignore: omit_local_variable_types - final Card card = tester.firstWidget( + final RoundedCard card = tester.firstWidget( find.byKey( ValueKey('annotationCard'), ), @@ -136,14 +136,14 @@ void main() { testDrug.guidelines.first.annotations.warningLevel.color, ); - context = tester.element(find.byType(Tooltip).first); - // test that drug activity can be set - final checkbox = tester.widget(find.byType(CheckboxListTile)) - as CheckboxListTile; - expect(checkbox.onChanged, isNotNull); + final activitySelection = tester.firstWidget( + find.byType(DropdownButton) + ) as DropdownButton; + expect(activitySelection.onChanged, isNotNull); // test tooltips + context = tester.element(find.byType(Tooltip).first); expect( find.byTooltip(context.l10n.drugs_page_tooltip_guideline), findsOneWidget, @@ -202,9 +202,10 @@ void main() { ), ); - final checkbox = tester.widget(find.byType(CheckboxListTile)) - as CheckboxListTile; - expect(checkbox.onChanged, isNull); + final activitySelection = tester.firstWidget( + find.byType(DropdownButton) + ) as DropdownButton; + expect(activitySelection.onChanged, isNull); }); }); } diff --git a/app/integration_test/faq_test.dart b/app/integration_test/faq_test.dart index 57f91406c..f54190a66 100644 --- a/app/integration_test/faq_test.dart +++ b/app/integration_test/faq_test.dart @@ -14,7 +14,7 @@ void main() { final faqWidget = MaterialApp.router( routeInformationParser: appRouter.defaultRouteParser(), routerDelegate: appRouter.delegate( - initialDeepLink: 'main/faq', + deepLinkBuilder: (_) => DeepLink.path('/main/faq'), ), localizationsDelegates: [ AppLocalizations.delegate, diff --git a/app/integration_test/login_test.dart b/app/integration_test/login_test.dart index ed077c69b..76f9dbbfc 100644 --- a/app/integration_test/login_test.dart +++ b/app/integration_test/login_test.dart @@ -8,8 +8,8 @@ import 'package:integration_test/integration_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:provider/provider.dart'; -class MockLoginCubit extends MockCubit - implements LoginPageCubit {} +class MockLoginCubit extends MockCubit + implements LoginCubit {} void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -20,7 +20,7 @@ void main() { group('integration tests for the login page', () { testWidgets('test loading state', (tester) async { when(() => mockLoginCubit.state).thenReturn( - LoginPageState.loadingUserData(), + LoginState.loadingUserData(), ); await tester.pumpWidget( @@ -44,7 +44,7 @@ void main() { testWidgets('test error state', (tester) async { when(() => mockLoginCubit.state).thenReturn( - LoginPageState.error('Some error'), + LoginState.error('Some error'), ); await tester.pumpWidget( @@ -70,7 +70,7 @@ void main() { testWidgets('test loaded state', (tester) async { when(() => mockLoginCubit.state).thenReturn( - LoginPageState.loadedUserData(), + LoginState.loadedUserData(), ); await tester.pumpWidget( @@ -97,7 +97,7 @@ void main() { testWidgets('test initial state', (tester) async { when(() => mockLoginCubit.state).thenReturn( - LoginPageState.initial(), + LoginState.initial(), ); await tester.pumpWidget( diff --git a/app/integration_test/main_page_test.dart b/app/integration_test/main_page_test.dart index 992acb46f..be977bd12 100644 --- a/app/integration_test/main_page_test.dart +++ b/app/integration_test/main_page_test.dart @@ -1,4 +1,3 @@ -import 'package:app/common/models/drug/cached_drugs.dart'; import 'package:app/common/module.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -25,7 +24,7 @@ void main() { child: MaterialApp.router( routeInformationParser: appRouter.defaultRouteParser(), routerDelegate: appRouter.delegate( - initialDeepLink: 'main', + deepLinkBuilder: (_) => DeepLink.path('/main'), ), localizationsDelegates: [ AppLocalizations.delegate, diff --git a/app/integration_test/settings_test.dart b/app/integration_test/more_test.dart similarity index 95% rename from app/integration_test/settings_test.dart rename to app/integration_test/more_test.dart index 8954b95e7..02b7b2633 100644 --- a/app/integration_test/settings_test.dart +++ b/app/integration_test/more_test.dart @@ -15,7 +15,7 @@ void main() { debugShowCheckedModeBanner: false, routeInformationParser: appRouter.defaultRouteParser(), routerDelegate: appRouter.delegate( - initialDeepLink: 'main/settings', + deepLinkBuilder: (_) => DeepLink.path('/main/more'), ), localizationsDelegates: [ AppLocalizations.delegate, @@ -65,7 +65,7 @@ void main() { findsOneWidget, ); - context.router.navigateBack(); + context.router.back(); await tester.pumpAndSettle(); // test privacy policy @@ -77,7 +77,7 @@ void main() { findsOneWidget, ); - context.router.navigateBack(); + context.router.back(); await tester.pumpAndSettle(); // test terms and conditions diff --git a/app/integration_test/onboarding_test.dart b/app/integration_test/onboarding_test.dart index 6fe261d95..10f1f4345 100644 --- a/app/integration_test/onboarding_test.dart +++ b/app/integration_test/onboarding_test.dart @@ -1,5 +1,5 @@ import 'package:app/common/module.dart'; -import 'package:app/onboarding/pages/onboarding.dart'; +import 'package:app/onboarding/module.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 20a82c12e..df08f0612 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -2,24 +2,26 @@ PODS: - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_secure_storage (3.3.1): + - flutter_secure_storage (6.0.0): - Flutter - flutter_share (0.0.1): - Flutter - - flutter_web_auth (0.4.1): + - flutter_web_auth (0.5.0): - Flutter - integration_test (0.0.1): - Flutter - package_info_plus (0.4.5): - Flutter - - path_provider_ios (0.0.1): + - path_provider_foundation (0.0.1): - Flutter + - FlutterMacOS - printing (1.0.0): - Flutter - sensors_plus (0.0.1): - Flutter - - shared_preferences_ios (0.0.1): + - shared_preferences_foundation (0.0.1): - Flutter + - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter @@ -31,10 +33,10 @@ DEPENDENCIES: - flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - printing (from `.symlinks/plugins/printing/ios`) - sensors_plus (from `.symlinks/plugins/sensors_plus/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: @@ -52,30 +54,30 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/integration_test/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" printing: :path: ".symlinks/plugins/printing/ios" sensors_plus: :path: ".symlinks/plugins/sensors_plus/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be flutter_share: 4be0208963c60b537e6255ed2ce1faae61cd9ac2 - flutter_web_auth: 09a0abd245f1a07a3ff4dcf1247a048d89ee12a9 - integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - printing: eafa00acb682c0ca029d4d98d0798f55a1e27102 + flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d + integration_test: 13825b8a9334a850581300559b8839134b124670 + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + printing: 233e1b73bd1f4a05615548e9b5a324c98588640b sensors_plus: 5717760720f7e6acd96fdbd75b7428f5ad755ec2 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj index 51341095d..c599db8d7 100644 --- a/app/ios/Runner.xcodeproj/project.pbxproj +++ b/app/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -226,6 +226,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e1..b52b2e698 100644 --- a/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ LSSupportsOpeningDocumentsInPlace UIFileSharingEnabled - + UIApplicationSupportsIndirectInputEvents + FLTEnableImpeller + diff --git a/app/lib/common/widgets/app.dart b/app/lib/app.dart similarity index 57% rename from app/lib/common/widgets/app.dart rename to app/lib/app.dart index 9ba3bfd21..35709ac32 100644 --- a/app/lib/common/widgets/app.dart +++ b/app/lib/app.dart @@ -1,37 +1,24 @@ import 'package:flutter_localizations/flutter_localizations.dart'; -import '../models/metadata.dart'; -import '../module.dart' hide MetaData; +import 'common/module.dart'; class PharMeApp extends StatelessWidget { factory PharMeApp() => _instance; - PharMeApp._({Key? key}) : super(key: key); + PharMeApp._(); static final _instance = PharMeApp._(); static GlobalKey get navigatorKey => _instance._appRouter.navigatorKey; final _appRouter = AppRouter(); - final _isLoggedIn = MetaData.instance.isLoggedIn ?? false; - final _onboardingDone = MetaData.instance.onboardingDone ?? false; - final _initialDrugSelectionDone = - MetaData.instance.initialDrugSelectionDone ?? false; @override Widget build(BuildContext context) { return MaterialApp.router( debugShowCheckedModeBanner: false, routeInformationParser: _appRouter.defaultRouteParser(), - routerDelegate: _appRouter.delegate( - initialDeepLink: !_isLoggedIn - ? 'login' - : !_onboardingDone - ? 'onboarding' - : !_initialDrugSelectionDone - ? 'drugselection' - : 'main', - ), + routerDelegate: _appRouter.delegate(deepLinkBuilder: getInitialRoute), theme: PharMeTheme.light, localizationsDelegates: [ AppLocalizations.delegate, diff --git a/app/lib/common/models/module.dart b/app/lib/common/models/module.dart index 30f7b4735..a1c47b710 100644 --- a/app/lib/common/models/module.dart +++ b/app/lib/common/models/module.dart @@ -1,4 +1,5 @@ export 'anni_response.dart'; +export 'drug/cached_drugs.dart'; export 'drug/drug.dart'; export 'drug/drug_inhibitors.dart'; export 'drug/guideline.dart'; diff --git a/app/lib/common/pages/drug/widgets/adaptive_dialog.dart b/app/lib/common/pages/drug/widgets/adaptive_dialog.dart deleted file mode 100644 index 48a7374a3..000000000 --- a/app/lib/common/pages/drug/widgets/adaptive_dialog.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -import '../../../module.dart'; - -class AdaptiveAlertDialog extends StatelessWidget { - const AdaptiveAlertDialog({ - Key? key, - required this.title, - required this.content, - required this.actions, - }) : super(key: key); - - final String title; - final Widget? content; - final List actions; - - @override - Widget build(BuildContext context) { - switch (getPlatform()) { - case SupportedPlatform.android: - return AlertDialog( - title: Text(title), - content: content, - actions: actions, - ); - case SupportedPlatform.ios: - return CupertinoAlertDialog( - title: Text(title), - content: Card( - color: Colors.transparent, - elevation: 0, - child: content, - ), - actions: actions, - ); - } - } -} - -class AdaptiveDialogAction extends StatelessWidget { - const AdaptiveDialogAction({ - Key? key, - this.isDefault = false, - this.isDestructive = false, - this.onPressed, - required this.text, - }) : super(key: key); - - final bool isDefault; - final bool isDestructive; - final void Function()? onPressed; - final String text; - - @override - Widget build(BuildContext context) { - switch (getPlatform()) { - case SupportedPlatform.android: - return TextButton( - onPressed: onPressed, - child: Text(text, style: onPressed != null - ? isDestructive - ? TextStyle(color: PharMeTheme.errorColor) - : TextStyle(color: PharMeTheme.primaryColor) - : TextStyle(color: PharMeTheme.onSurfaceColor)), - ); - case SupportedPlatform.ios: - return CupertinoDialogAction( - isDefaultAction: isDefault, - isDestructiveAction: isDestructive, - onPressed: onPressed, - child: Text(text), - ); - } - } -} - -Future showAdaptiveDialog({ - required BuildContext context, - required Widget Function(BuildContext) builder, -}) { - switch (getPlatform()) { - case SupportedPlatform.android: - return showDialog(context: context, builder: builder); - case SupportedPlatform.ios: - return showCupertinoDialog(context: context, builder: builder); - } -} \ No newline at end of file diff --git a/app/lib/common/pages/drug/widgets/annotation_cards/drug.dart b/app/lib/common/pages/drug/widgets/annotation_cards/drug.dart deleted file mode 100644 index 06289ca16..000000000 --- a/app/lib/common/pages/drug/widgets/annotation_cards/drug.dart +++ /dev/null @@ -1,96 +0,0 @@ -import '../../../../module.dart'; -import '../adaptive_dialog.dart'; -import '../sub_header.dart'; - -class DrugAnnotationCard extends StatelessWidget { - const DrugAnnotationCard( - this.drug, { - required this.isActive, - required this.setActivity, - this.disabled = false, - }); - - final Drug drug; - final bool isActive; - final void Function(bool?) setActivity; - final bool disabled; - - @override - Widget build(BuildContext context) { - return RoundedCard( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SubHeader(context.l10n.drugs_page_header_druginfo), - SizedBox(height: 12), - Text(drug.annotations.indication), - SizedBox(height: 8), - Table(defaultColumnWidth: IntrinsicColumnWidth(), children: [ - _buildRow(context.l10n.drugs_page_header_drugclass, - drug.annotations.drugclass), - if (drug.annotations.brandNames.isNotEmpty) ...[ - _buildRow(context.l10n.drugs_page_header_synonyms, - drug.annotations.brandNames.join(', ')), - ] - ]), - SizedBox(height: 4), - if (isInhibitor(drug.name)) ...[ - SizedBox(height: 8), - Text(context.l10n.drugs_page_is_inhibitor( - drug.name, - inhibitedGenes(drug).join(', '), - )), - ], - Divider(color: PharMeTheme.borderColor), - SizedBox(height: 4), - SubHeader(context.l10n.drugs_page_header_active), - CheckboxListTile( - activeColor: PharMeTheme.primaryColor, - title: Text(context.l10n.drugs_page_active), - value: isActive, - onChanged: disabled ? null : (newValue) => { - if (isInhibitor(drug.name)) { - showAdaptiveDialog( - context: context, - builder: (context) => AdaptiveAlertDialog( - title: context.l10n.drugs_page_active_warn_header, - content: Text(context.l10n.drugs_page_active_warn), - actions: [ - AdaptiveDialogAction( - isDefault: true, - onPressed: () => Navigator.pop(context, 'Cancel'), - text: context.l10n.action_cancel, - ), - AdaptiveDialogAction( - isDestructive: true, - onPressed: () { - Navigator.pop(context, 'OK'); - setActivity(newValue); - }, - text: context.l10n.action_continue, - ), - ], - ), - ) - } else { - setActivity(newValue) - } - }, - controlAffinity: ListTileControlAffinity.leading, - ), - ], - ), - ), - ); - } - - TableRow _buildRow(String key, String value) => TableRow(children: [ - Padding( - padding: EdgeInsets.fromLTRB(0, 4, 12, 4), - child: Text(key, - style: PharMeTheme.textTheme.bodyMedium! - .copyWith(fontWeight: FontWeight.bold))), - Padding(padding: EdgeInsets.fromLTRB(0, 4, 0, 4), child: Text(value)), - ]); -} diff --git a/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart b/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart deleted file mode 100644 index 9eb3b6c31..000000000 --- a/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:url_launcher/url_launcher.dart'; - -import '../../../../module.dart'; -import '../sub_header.dart'; - -class GuidelineAnnotationCard extends StatelessWidget { - const GuidelineAnnotationCard(this.drug); - - final Drug drug; - - @override - Widget build(BuildContext context) { - return RoundedCard( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), - child: SingleChildScrollView( - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(context), - SizedBox(height: 12), - if (drug.userGuideline != null) ...[ - _buildCard(context), - SizedBox(height: 8), - Divider(color: PharMeTheme.borderColor), - SizedBox(height: 8), - _buildSourcesSection(context), - SizedBox(height: 12), - ] - else ...[ - _buildCard(context), - SizedBox(height: 16), - ], - ]), - ), - ); - } - - Widget _buildCard(BuildContext context) { - final upperCardText = drug.userGuideline?.annotations.implication ?? - context.l10n.drugs_page_no_guidelines_for_phenotype_implication( - drug.name - ); - final lowerCardText = drug.userGuideline?.annotations.recommendation ?? - context.l10n.drugs_page_no_guidelines_for_phenotype_recommendation; - return Card( - key: Key('annotationCard'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - color: drug.warningLevel.color, - child: Padding( - padding: EdgeInsets.all(12), - child: - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Icon( - drug.warningLevel.icon, - color: PharMeTheme.onSurfaceText, - ), - SizedBox(width: 12), - Flexible( - child: Text( - upperCardText, - style: PharMeTheme.textTheme.bodyMedium, - ), - ) - ]), - SizedBox(height: 12), - Text( - lowerCardText, - style: PharMeTheme.textTheme.bodyMedium, - ), - ]))); - } - - Widget _buildHeader(BuildContext context) { - var headerContent = ''; - var headerStyle = PharMeTheme.textTheme.bodyLarge!; - if (drug.userGuideline == null && drug.guidelines.isEmpty) { - headerContent = context.l10n.drugs_page_guidelines_empty(drug.name); - headerStyle = headerStyle.copyWith(fontStyle: FontStyle.italic); - } else { - final genes = drug.userGuideline?.lookupkey.keys ?? - drug.guidelines.first.lookupkey.keys; - final geneDescriptions = genes.map((geneSymbol) { - final phenotypeInformation = UserData.phenotypeFor( - geneSymbol, - context, - drug: drug.name, - ); - var description = '$geneSymbol: ${phenotypeInformation.phenotype}'; - if (phenotypeInformation.adaptionText.isNotNullOrBlank) { - description = '$description (${phenotypeInformation.adaptionText})'; - } - return description; - }); - headerContent = geneDescriptions.join('\n'); - } - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - SubHeader(context.l10n.drugs_page_your_genome), - SizedBox(height: 12), - Text( - headerContent, - style: headerStyle, - ), - ]); - } - - Widget _buildSourcesSection(BuildContext context) { - // pipes are illegal characters in URLs so please - // - forgive the cheap hack or - // - refactor by making a custom object and defining equality for it :) - final sources = drug.userGuideline!.externalData - .map((data) => '${data.source}|${data.guidelineUrl}') - .toSet(); - return Column(children: [ - SubHeader( - context.l10n.drugs_page_header_further_info, - ), - SizedBox(height: 12), - ...sources.map( - (source) => GestureDetector( - onTap: () => _launchUrl(Uri.parse(source.split('|')[1])), - child: Card( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - color: PharMeTheme.onSurfaceColor, - child: Padding( - padding: EdgeInsets.all(12), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - Flexible( - child: Text(context.l10n - .drugs_page_sources_description(source.split('|')[0])), - ), - Icon(Icons.chevron_right_rounded) - ]), - ), - ), - ), - ), - ]); - } -} - -Future _launchUrl(Uri url) async { - if (!await launchUrl(url)) throw Error(); -} diff --git a/app/lib/common/pages/drug/widgets/module.dart b/app/lib/common/pages/drug/widgets/module.dart deleted file mode 100644 index a95ff23a8..000000000 --- a/app/lib/common/pages/drug/widgets/module.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'annotation_cards/drug.dart'; -export 'annotation_cards/guideline.dart'; -export 'disclaimer.dart'; -export 'sub_header.dart'; diff --git a/app/lib/common/routing/router.dart b/app/lib/common/routing/router.dart index 2545fa7f3..95d0bffee 100644 --- a/app/lib/common/routing/router.dart +++ b/app/lib/common/routing/router.dart @@ -1,31 +1,34 @@ -import '../../common/module.dart'; +import '../../drug/module.dart'; import '../../drug_selection/module.dart'; import '../../faq/module.dart'; import '../../login/module.dart'; +import '../../main/module.dart'; +import '../../more/module.dart'; import '../../onboarding/module.dart'; import '../../report/module.dart'; import '../../search/module.dart'; -import '../../settings/module.dart'; -import '../pages/main/main.dart'; +import '../module.dart'; part 'router.gr.dart'; -@MaterialAutoRouter( - replaceInRouteName: 'Page,Route', - routes: [ - drugSelectionRoutes, - loginRoutes, - onboardingRoutes, - AutoRoute( - path: 'main', - page: MainPage, +@AutoRouterConfig() +class AppRouter extends _$AppRouter { + @override + RouteType get defaultRouteType => RouteType.adaptive(); + @override + List get routes => [ + drugSelectionRoute(), + loginRoute(), + onboardingRoute(), + mainRoute( children: [ - reportRoutes, - searchRoutes, - settingsRoutes, - faqRoutes, + reportRoute(children: [ geneRoute(), drugRoute() ]), + searchRoute(children: [ drugRoute() ]), + faqRoute(), + moreRoute( + children: [ aboutRoute(), termsRoute(), privacyRoute() ], + ), ], ), - ], -) -class AppRouter extends _$AppRouter {} + ]; +} diff --git a/app/lib/common/services.dart b/app/lib/common/services.dart index 6f3ee20b6..54d0ae633 100644 --- a/app/lib/common/services.dart +++ b/app/lib/common/services.dart @@ -1,7 +1,6 @@ import 'package:hive_flutter/hive_flutter.dart'; -import 'models/drug/cached_drugs.dart'; -import 'models/module.dart'; +import 'module.dart'; Future initServices() async { await Hive.initFlutter(); diff --git a/app/lib/common/theme.dart b/app/lib/common/theme.dart index 9011ede43..b49c948bd 100644 --- a/app/lib/common/theme.dart +++ b/app/lib/common/theme.dart @@ -81,6 +81,7 @@ class PharMeTheme { static const backgroundColor = Colors.white; static const errorColor = Color(0xccf52a2a); static final borderColor = Colors.black.withOpacity(.2); + static final iconColor = darkenColor(PharMeTheme.onSurfaceText, -0.1); static const smallSpace = 8.0; static const smallToMediumSpace = 12.0; @@ -88,6 +89,9 @@ class PharMeTheme { static const mediumToLargeSpace = 24.0; static const largeSpace = 32.0; + static const outerCardRadius = mediumSpace; + static const innerCardRadius = smallToMediumSpace; + static final appBarTheme = AppBarTheme( backgroundColor: surfaceColor, foregroundColor: onSurfaceText, diff --git a/app/lib/common/utilities/color_utils.dart b/app/lib/common/utilities/color_utils.dart index e239cb534..f8450573f 100644 --- a/app/lib/common/utilities/color_utils.dart +++ b/app/lib/common/utilities/color_utils.dart @@ -3,7 +3,7 @@ import '../module.dart'; // From https://stackoverflow.com/a/58604669 Color darkenColor(Color color, [double amount = 0.1]) { - assert(amount >= 0 && amount <= 1); + assert(amount >= -1 && amount <= 1); final hsl = HSLColor.fromColor(color); final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); return hslDark.toColor(); diff --git a/app/lib/common/utilities/drug_utils.dart b/app/lib/common/utilities/drug_utils.dart index 9cd910351..6620203ef 100644 --- a/app/lib/common/utilities/drug_utils.dart +++ b/app/lib/common/utilities/drug_utils.dart @@ -1,9 +1,8 @@ import 'dart:convert'; import 'package:http/http.dart'; -import '../models/drug/cached_drugs.dart'; +import '../../app.dart'; import '../module.dart'; -import '../pages/drug/widgets/adaptive_dialog.dart'; Future updateCachedDrugs() async { if (UserData.instance.lookups == null) throw Exception(); @@ -33,12 +32,11 @@ Future updateCachedDrugs() async { // ignore: use_build_context_synchronously await showAdaptiveDialog( context: context, - builder: (context) => AdaptiveAlertDialog( + builder: (context) => DialogWrapper( title: context.l10n.update_warning_title, - content: Text(context.l10n.update_warning_body), + content: DialogContentText(context.l10n.update_warning_body), actions: [ - AdaptiveDialogAction( - isDefault: true, + DialogAction( onPressed: () => Navigator.pop(context), text: context.l10n.action_continue, ), diff --git a/app/lib/common/utilities/genome_data.dart b/app/lib/common/utilities/genome_data.dart index 1f8d5f58e..bf9c4a056 100644 --- a/app/lib/common/utilities/genome_data.dart +++ b/app/lib/common/utilities/genome_data.dart @@ -4,7 +4,6 @@ import 'dart:convert'; import 'package:http/http.dart'; import '../constants.dart'; -import '../models/drug/cached_drugs.dart'; import '../models/module.dart'; Future fetchAndSaveDiplotypesAndActiveDrugs( @@ -31,7 +30,9 @@ Future _saveDiplotypeAndActiveDrugsResponse( diplotypesFromHTTPResponse(response).filterValidDiplotypes(); final activeDrugList = activeDrugsFromHTTPResponse(response); - UserData.instance.diplotypes = {for (var d in diplotypes) d.gene: d}; + UserData.instance.diplotypes = { + for (final diplotype in diplotypes) diplotype.gene: diplotype + }; await UserData.save(); await activeDrugs.setList(activeDrugList); // invalidate cached drugs because lookups may have changed and we need to diff --git a/app/lib/common/utilities/module.dart b/app/lib/common/utilities/module.dart index b7529502e..20e5ed438 100644 --- a/app/lib/common/utilities/module.dart +++ b/app/lib/common/utilities/module.dart @@ -1,7 +1,9 @@ +export 'color_utils.dart'; export 'drug_utils.dart'; export 'genome_data.dart'; export 'material_colors.dart'; export 'networking_utils.dart'; +export 'pdf_utils.dart'; export 'platform_utils.dart'; export 'routing_utils.dart'; export 'string_utils.dart'; diff --git a/app/lib/common/utilities/pdf_utils.dart b/app/lib/common/utilities/pdf_utils.dart index 4d47b4598..750a84494 100644 --- a/app/lib/common/utilities/pdf_utils.dart +++ b/app/lib/common/utilities/pdf_utils.dart @@ -332,8 +332,7 @@ List _buildGuidelinePart( implication.key ), text: implication.value, - ))) - .toList(), + ))), _buildTextSpacer(), _PdfSegment( child: _PdfDescription( diff --git a/app/lib/common/utilities/routing_utils.dart b/app/lib/common/utilities/routing_utils.dart index 2fe94e68a..2bd7d9241 100644 --- a/app/lib/common/utilities/routing_utils.dart +++ b/app/lib/common/utilities/routing_utils.dart @@ -1,5 +1,21 @@ import '../module.dart'; +DeepLink getInitialRoute(_) { + final isLoggedIn = MetaData.instance.isLoggedIn ?? false; + final onboardingDone = MetaData.instance.onboardingDone ?? false; + final initialDrugSelectionDone = + MetaData.instance.initialDrugSelectionDone ?? false; + late String path; + path = !isLoggedIn + ? '/login' + : !onboardingDone + ? '/onboarding' + : !initialDrugSelectionDone + ? '/drugselection' + : '/main'; + return DeepLink.path(path); +} + // Replace whole stack, see https://stackoverflow.com/a/73784156 Future overwriteRoutes( BuildContext context, diff --git a/app/lib/common/widgets/checkbox_list_tile_wrapper.dart b/app/lib/common/widgets/checkbox_list_tile_wrapper.dart new file mode 100644 index 000000000..d01fdd4ab --- /dev/null +++ b/app/lib/common/widgets/checkbox_list_tile_wrapper.dart @@ -0,0 +1,38 @@ +import '../module.dart'; + +class CheckboxListTileWrapper extends StatelessWidget { + const CheckboxListTileWrapper({ + super.key, + required this.title, + required this.isChecked, + required this.onChanged, + this.subtitle, + this.isEnabled = true, + this.contentPadding, + }); + + final String title; + final String? subtitle; + final bool isChecked; + // ignore: avoid_positional_boolean_parameters + final void Function(bool?)? onChanged; + final bool isEnabled; + final EdgeInsetsGeometry? contentPadding; + + @override + Widget build(BuildContext context) { + return ListTile( + enabled: isEnabled, + title: Text(title, style: PharMeTheme.textTheme.bodyLarge), + subtitle: subtitle != null + ? Text(subtitle!, style: PharMeTheme.textTheme.bodyMedium) + : null, + contentPadding: contentPadding, + leading: CheckboxWrapper( + isEnabled: isEnabled, + isChecked: isChecked, + onChanged: onChanged, + ), + ); + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/checkbox_wrapper.dart b/app/lib/common/widgets/checkbox_wrapper.dart new file mode 100644 index 000000000..0d2f349a0 --- /dev/null +++ b/app/lib/common/widgets/checkbox_wrapper.dart @@ -0,0 +1,28 @@ +import '../module.dart'; + +class CheckboxWrapper extends StatelessWidget { + const CheckboxWrapper({ + super.key, + required this.isChecked, + required this.onChanged, + this.isEnabled = true, + }); + + final bool isChecked; + // ignore: avoid_positional_boolean_parameters + final void Function(bool?)? onChanged; + final bool isEnabled; + + @override + Widget build(BuildContext context) { + return Checkbox.adaptive( + value: isChecked, + onChanged: isEnabled ? onChanged : null, + activeColor: PharMeTheme.primaryColor, + checkColor: Colors.white, + side: isChecked || !isEnabled + ? null + : BorderSide(color: darkenColor(PharMeTheme.iconColor, -0.15)), + ); + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/context_menu.dart b/app/lib/common/widgets/context_menu.dart deleted file mode 100644 index 70ee1a910..000000000 --- a/app/lib/common/widgets/context_menu.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:popover/popover.dart'; - -import '../module.dart'; - -class ContextMenu extends StatelessWidget { - const ContextMenu({ - super.key, - this.headerItem, - required this.items, - required this.child, - }); - - final Widget? headerItem; - final List items; - final Widget child; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - showPopover( - context: context, - bodyBuilder: (context) => Padding( - padding: EdgeInsets.symmetric( - horizontal: PharMeTheme.smallToMediumSpace - ), - child: Container( - decoration: BoxDecoration( - color: PharMeTheme.onSurfaceColor, - borderRadius: BorderRadius.circular(8), - ), - child: IntrinsicWidth( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildContent(context), - ), - ), - ), - ), - direction: PopoverDirection.bottom, - arrowHeight: 0, - arrowWidth: 0, - transitionDuration: Duration(milliseconds: 100), - barrierColor: Color.fromRGBO(0, 0, 0, 0.05), - backgroundColor: Color.fromRGBO(1, 1, 1, 0), - shadow: [], - ); - }, - child: child); - } - - Widget _itemContainer( - Widget item, - { - bool showBorder = true, - double padding = PharMeTheme.smallToMediumSpace, - } - ) { - return Container( - decoration: showBorder ? BoxDecoration( - border: Border( - bottom: BorderSide( - width: 0.5, - color: PharMeTheme.borderColor - ), - ), - ) : null, - child: Padding( - padding: EdgeInsets.all(padding), - child: item, - ) - ); - } - - List _buildContent(BuildContext context) { - final body = items.mapIndexed( - (index, item) => (index == items.count() - 1) - ? _itemContainer(item, showBorder: false) - : _itemContainer(item) - ).toList(); - return headerItem != null - ? [ - _itemContainer( - Row( - children: [headerItem!] - ), - padding: PharMeTheme.mediumSpace, - showBorder: false, - ), - ...body, - ] - : body; - } -} - -class ContextMenuCheckmark extends StatelessWidget { - const ContextMenuCheckmark( - {super.key, - required this.label, - required this.setState, - this.initialState = false}); - - final String label; - final void Function(bool) setState; - final bool initialState; - - @override - Widget build(BuildContext context) { - var state = initialState; - return StatefulBuilder( - builder: (context, rebuild) => GestureDetector( - onTap: () { - rebuild(() { - state = !state; - setState(state); - }); - }, - child: Row( - children: [ - if (state) - Icon(Icons.check_box, size: PharMeTheme.mediumSpace) - else - Icon(Icons.check_box_outline_blank, size: PharMeTheme.mediumSpace), - SizedBox(width: PharMeTheme.smallSpace), - Expanded(child: Text(label)), - ], - ), - ), - ); - } -} diff --git a/app/lib/common/widgets/dialog_action.dart b/app/lib/common/widgets/dialog_action.dart new file mode 100644 index 000000000..7c5155dcc --- /dev/null +++ b/app/lib/common/widgets/dialog_action.dart @@ -0,0 +1,37 @@ +import 'package:flutter/cupertino.dart'; + +import '../module.dart'; + +class DialogAction extends StatelessWidget { + const DialogAction({ + super.key, + this.isDestructive = false, + this.onPressed, + required this.text, + }); + + final bool isDestructive; + final void Function()? onPressed; + final String text; + + @override + Widget build(BuildContext context) { + switch (getPlatform()) { + case SupportedPlatform.ios: + return CupertinoDialogAction( + isDestructiveAction: isDestructive, + onPressed: onPressed, + child: Text(text), + ); + default: + return TextButton( + onPressed: onPressed, + child: Text(text, style: onPressed != null + ? isDestructive + ? TextStyle(color: PharMeTheme.errorColor) + : TextStyle(color: PharMeTheme.primaryColor) + : TextStyle(color: PharMeTheme.onSurfaceColor)), + ); + } + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/dialog_content_text.dart b/app/lib/common/widgets/dialog_content_text.dart new file mode 100644 index 000000000..f3185f360 --- /dev/null +++ b/app/lib/common/widgets/dialog_content_text.dart @@ -0,0 +1,14 @@ +import '../module.dart'; + +class DialogContentText extends StatelessWidget { + const DialogContentText(this.text, { + super.key, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Text(text, style: PharMeTheme.textTheme.bodyLarge); + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/dialog_wrapper.dart b/app/lib/common/widgets/dialog_wrapper.dart new file mode 100644 index 000000000..b710f4d65 --- /dev/null +++ b/app/lib/common/widgets/dialog_wrapper.dart @@ -0,0 +1,31 @@ +import '../module.dart'; + +class DialogWrapper extends StatelessWidget { + const DialogWrapper({ + super.key, + required this.title, + required this.content, + required this.actions, + }); + + final String title; + final Widget? content; + final List actions; + + @override + Widget build(BuildContext context) { + final materialContent = getPlatform() == SupportedPlatform.ios + ? Card( + color: Colors.transparent, + elevation: 0, + child: content, + ) + : content; + return AlertDialog.adaptive( + title: Text(title), + content: materialContent, + actions: actions, + elevation: 0, + ); + } +} diff --git a/app/lib/common/widgets/drug_list/cubit.dart b/app/lib/common/widgets/drug_list/cubit.dart index e94be55e9..6d20f631f 100644 --- a/app/lib/common/widgets/drug_list/cubit.dart +++ b/app/lib/common/widgets/drug_list/cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:freezed_annotation/freezed_annotation.dart'; -import '../../models/drug/cached_drugs.dart'; import '../../module.dart'; part 'cubit.freezed.dart'; diff --git a/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart b/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart index f44e850ab..dd21c9db0 100644 --- a/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart +++ b/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart @@ -33,8 +33,8 @@ class DrugCard extends StatelessWidget { required this.onTap, required this.drug, required this.showDrugInteractionIndicator, - Key? key, - }) : super(key: key); + super.key, + }); final VoidCallback onTap; final Drug drug; @@ -43,12 +43,10 @@ class DrugCard extends StatelessWidget { @override Widget build(BuildContext context) { final drugName = formatDrugName(drug, showDrugInteractionIndicator); - return Padding( - padding: EdgeInsets.symmetric(vertical: PharMeTheme.smallSpace / 2), - child: RoundedCard( + return RoundedCard( onTap: onTap, - padding: EdgeInsets.all(8), - radius: 16, + innerPadding: EdgeInsets.all(PharMeTheme.smallSpace * 1.25), + radius: 18, color: drug.warningLevel.color, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -66,15 +64,15 @@ class DrugCard extends StatelessWidget { .copyWith(fontWeight: FontWeight.bold), ), ]), - SizedBox(height: 4), + SizedBox(height: PharMeTheme.smallSpace / 2), if (drug.annotations.brandNames.isNotEmpty) ...[ - SizedBox(width: 4), + SizedBox(width: PharMeTheme.smallSpace / 2), Text( '(${drug.annotations.brandNames.join(', ')})', style: PharMeTheme.textTheme.titleMedium, ), ], - SizedBox(height: 8), + SizedBox(height: PharMeTheme.smallSpace * 0.75), Text( drug.annotations.drugclass, style: PharMeTheme.textTheme.titleSmall, @@ -85,7 +83,6 @@ class DrugCard extends StatelessWidget { Icon(Icons.chevron_right_rounded), ], ), - ), ); } } diff --git a/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart b/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart index cbffffd57..af81e3452 100644 --- a/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart +++ b/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart @@ -33,19 +33,19 @@ List buildDrugCheckboxList( ); return [ SubheaderDivider( - context.l10n.drug_selection_subheader_active_drugs, + text: context.l10n.drug_selection_subheader_active_drugs, key: Key('header-active'), ), ...activeDrugsList, SubheaderDivider( - context.l10n.drug_selection_subheader_all_drugs, + text: context.l10n.drug_selection_subheader_all_drugs, key: Key('header-all'), ), ...allDrugsList, ]; } -List _buildCheckboxList( +List _buildCheckboxList( List drugs, Map buildParams, bool showDrugInteractionIndicator, @@ -54,14 +54,14 @@ List _buildCheckboxList( final onCheckboxChange = buildParams['onCheckboxChange']; final checkboxesEnabled = buildParams['checkboxesEnabled']; return drugs.map( - (drug) => CheckboxListTile( + (drug) => CheckboxListTileWrapper( key: Key('drug-checkbox-tile-${drug.name}-$keyPrefix'), - enabled: checkboxesEnabled, - value: drug.isActive, + isEnabled: checkboxesEnabled, + isChecked: drug.isActive, onChanged: (value) => onCheckboxChange(drug, value), - title: Text(formatDrugName(drug, showDrugInteractionIndicator)), + title: formatDrugName(drug, showDrugInteractionIndicator), subtitle: (drug.annotations.brandNames.isNotEmpty) ? - Text('(${drug.annotations.brandNames.join(", ")})') : + '(${drug.annotations.brandNames.join(", ")})' : null, ) ).toList(); diff --git a/app/lib/common/widgets/drug_search.dart b/app/lib/common/widgets/drug_search.dart index fdff3a40c..cc14c956a 100644 --- a/app/lib/common/widgets/drug_search.dart +++ b/app/lib/common/widgets/drug_search.dart @@ -3,11 +3,11 @@ import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; import '../../../common/module.dart'; -import '../../common/pages/drug/widgets/tooltip_icon.dart'; +import '../../drug/widgets/tooltip_icon.dart'; class DrugSearch extends HookWidget { DrugSearch({ - Key? key, + super.key, required this.showFilter, required this.buildDrugItems, required this.showDrugInteractionIndicator, @@ -15,8 +15,7 @@ class DrugSearch extends HookWidget { this.keepPosition = false, this.drugItemsBuildParams, DrugListCubit? cubit, - }) : cubit = cubit ?? DrugListCubit(), - super(key: key); + }) : cubit = cubit ?? DrugListCubit(); final bool showFilter; final bool useDrugClass; @@ -66,6 +65,13 @@ class DrugSearch extends HookWidget { ] ), SizedBox(height: PharMeTheme.smallSpace), + if (showDrugInteractionIndicator) + PageIndicatorExplanation( + context.l10n.search_page_indicator_explanation( + drugInteractionIndicatorName, + drugInteractionIndicator + ), + ), scrollList( keepPosition: keepPosition, buildDrugList( @@ -80,7 +86,6 @@ class DrugSearch extends HookWidget { useDrugClass: useDrugClass, ) ), - ..._maybeShowDrugInteractionExplanation(context), ], ); } @@ -89,53 +94,42 @@ class DrugSearch extends HookWidget { ); } - List _maybeShowDrugInteractionExplanation(BuildContext context) { - if (!showDrugInteractionIndicator) return []; - return [ - SizedBox(height: PharMeTheme.smallSpace), - Text( - context.l10n.search_page_indicator_explanation( - drugInteractionIndicatorName, - drugInteractionIndicator - ) - ), - ]; - } - Widget buildFilter(BuildContext context) { final cubit = context.read(); final filter = cubit.filter; - return ContextMenu( + return FilterMenu( items: [ - ContextMenuCheckmark( - label: context.l10n.search_page_filter_only_active, - // Invert state as filter has opposite meaning ('only show active' vs. - // 'show inactive') - setState: (state) => cubit.search(showInactive: !state), - initialState: filter != null && !filter.showInactive), - ...WarningLevel.values.filter((level) => level != WarningLevel.none) - .map((level) => ContextMenuCheckmark( - label: { + ...WarningLevel.values + .filter((level) => level != WarningLevel.none) + .map((level) => FilterMenuItem( + title: { WarningLevel.green: context.l10n.search_page_filter_green, WarningLevel.yellow: context.l10n.search_page_filter_yellow, WarningLevel.red: context.l10n.search_page_filter_red, }[level]!, - setState: (state) => cubit.search(showWarningLevel: {level: state}), - initialState: filter?.showWarningLevel[level] ?? false + updateSearch: ({ required isChecked }) => + cubit.search(showWarningLevel: { level: isChecked }), + isChecked: filter?.showWarningLevel[level] ?? false ) ), - ContextMenuCheckmark( - label: context.l10n.search_page_filter_only_with_guidelines, + FilterMenuItem( + title: context.l10n.search_page_filter_only_active, + // Invert state as filter has opposite meaning ('only show active' vs. + // 'show inactive') + updateSearch: ({ required isChecked }) => cubit.search(showInactive: !isChecked), + isChecked: !(filter?.showInactive ?? false) + ), + FilterMenuItem( + title: context.l10n.search_page_filter_only_with_guidelines, // Invert state as filter has opposite meaning ('show only with // guidelines' vs. 'show with unknown warning level') - setState: (state) => cubit.search( - showWarningLevel: {WarningLevel.none: !state} + updateSearch: ({ required isChecked }) => cubit.search( + showWarningLevel: { WarningLevel.none: !isChecked } ), - initialState: filter != null && - !filter.showWarningLevel[WarningLevel.none]!,) + isChecked: !(filter?.showWarningLevel[WarningLevel.none] ?? false), + ) ], - child: Padding( - padding: EdgeInsets.all(8), child: Icon(Icons.filter_list_rounded)), + iconData: Icons.filter_list_rounded, ); } } diff --git a/app/lib/common/widgets/filter_menu.dart b/app/lib/common/widgets/filter_menu.dart new file mode 100644 index 000000000..06fb25f23 --- /dev/null +++ b/app/lib/common/widgets/filter_menu.dart @@ -0,0 +1,57 @@ +import '../module.dart'; + +class FilterMenuItem { + FilterMenuItem({ + required this.title, + required this.updateSearch, + required bool isChecked, + }) : _isChecked = isChecked; + + final String title; + final void Function({ required bool isChecked }) updateSearch; + bool _isChecked; + + set checked(newValue) => _isChecked = newValue; + bool get checked => _isChecked; +} + +class FilterMenu extends HookWidget { + const FilterMenu({ + super.key, + this.headerItem, + required this.items, + required this.iconData, + }); + + final Widget? headerItem; + final List items; + final IconData iconData; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: Icon(iconData), + color: PharMeTheme.onSurfaceColor, + elevation: 0, + itemBuilder: (context) => items.map( + (item) => PopupMenuItem( + child: StatefulBuilder(builder: (context, setState) { + void toggleCheckbox([_]) { + final newValue = !item.checked; + setState(() => item.checked = newValue); + item.updateSearch(isChecked: newValue); + } + return ListTile( + title: Text(item.title), + leading: CheckboxWrapper( + isChecked: item.checked, + onChanged: toggleCheckbox, + ), + onTap: toggleCheckbox, + ); + }), + ) + ).toList(), + ); + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/full_width_button.dart b/app/lib/common/widgets/full_width_button.dart index 79022d9d2..5e73b9dc7 100644 --- a/app/lib/common/widgets/full_width_button.dart +++ b/app/lib/common/widgets/full_width_button.dart @@ -4,9 +4,9 @@ class FullWidthButton extends StatelessWidget { const FullWidthButton( this.text, this.action, { - Key? key, + super.key, this.enabled = true, - }) : super(key: key); + }); final bool enabled; final String text; @@ -19,13 +19,10 @@ class FullWidthButton extends StatelessWidget { child: ElevatedButton( onPressed: enabled ? action : null, style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(32), - ), - ), + backgroundColor: + MaterialStateProperty.all(PharMeTheme.primaryColor), ), - child: Text(text), + child: Text(text, style: PharMeTheme.textTheme.bodyLarge), ), ); } diff --git a/app/lib/common/widgets/headings.dart b/app/lib/common/widgets/headings.dart index ec83f8a33..8160c8623 100644 --- a/app/lib/common/widgets/headings.dart +++ b/app/lib/common/widgets/headings.dart @@ -5,8 +5,8 @@ import '../theme.dart'; class Heading extends StatelessWidget { const Heading( this.text, { - Key? key, - }) : super(key: key); + super.key, + }); final String text; diff --git a/app/lib/common/widgets/module.dart b/app/lib/common/widgets/module.dart index 70359f7f3..0da919361 100644 --- a/app/lib/common/widgets/module.dart +++ b/app/lib/common/widgets/module.dart @@ -1,9 +1,14 @@ -export 'app.dart'; -export 'context_menu.dart'; +export 'checkbox_list_tile_wrapper.dart'; +export 'checkbox_wrapper.dart'; +export 'dialog_action.dart'; +export 'dialog_content_text.dart'; +export 'dialog_wrapper.dart'; export 'drug_list/builder.dart'; export 'drug_list/cubit.dart'; +export 'filter_menu.dart'; export 'headings.dart'; export 'indicators.dart'; +export 'page_indicator_explanation.dart'; export 'page_scaffold.dart'; export 'radiant_gradient_mask.dart'; export 'rounded_card.dart'; diff --git a/app/lib/common/widgets/page_indicator_explanation.dart b/app/lib/common/widgets/page_indicator_explanation.dart new file mode 100644 index 000000000..522788879 --- /dev/null +++ b/app/lib/common/widgets/page_indicator_explanation.dart @@ -0,0 +1,19 @@ +import '../module.dart'; + +class PageIndicatorExplanation extends StatelessWidget { + const PageIndicatorExplanation(this.text); + + final String text; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + left: PharMeTheme.smallSpace, + right: PharMeTheme.smallSpace, + bottom: PharMeTheme.smallSpace, + ), + child: Text(text), + ); + } +} \ No newline at end of file diff --git a/app/lib/common/widgets/page_scaffold.dart b/app/lib/common/widgets/page_scaffold.dart index cbdf87860..d01d2b01c 100644 --- a/app/lib/common/widgets/page_scaffold.dart +++ b/app/lib/common/widgets/page_scaffold.dart @@ -9,6 +9,7 @@ AppBar? buildBarBottom(String? barBottom) { ? null : AppBar( automaticallyImplyLeading: false, + scrolledUnderElevation: 0, backgroundColor: PharMeTheme.appBarTheme.backgroundColor, elevation: PharMeTheme.appBarTheme.elevation, title: RichText( @@ -35,6 +36,7 @@ Scaffold pageScaffold({ key: key, body: CustomScrollView(slivers: [ SliverAppBar( + scrolledUnderElevation: 0, backgroundColor: PharMeTheme.appBarTheme.backgroundColor, foregroundColor: PharMeTheme.appBarTheme.foregroundColor, elevation: PharMeTheme.appBarTheme.elevation, @@ -71,6 +73,7 @@ Scaffold unscrollablePageScaffold({ title: buildTitle(title), actions: actions, bottom: buildBarBottom(barBottom), + scrolledUnderElevation: 0, ); return Scaffold( key: key, diff --git a/app/lib/common/widgets/rounded_card.dart b/app/lib/common/widgets/rounded_card.dart index 7436bf301..9c89f6b92 100644 --- a/app/lib/common/widgets/rounded_card.dart +++ b/app/lib/common/widgets/rounded_card.dart @@ -2,42 +2,50 @@ import '../module.dart'; class RoundedCard extends StatelessWidget { const RoundedCard({ - this.padding = const EdgeInsets.all(16), - this.color = PharMeTheme.surfaceColor, - this.radius = 20, + this.innerPadding, + this.outerVerticalPadding, + this.outerHorizontalPadding, + this.color, + this.radius, this.onTap, required this.child, + super.key, }); - final EdgeInsets padding; + final EdgeInsets? innerPadding; + final double? outerVerticalPadding; + final double? outerHorizontalPadding; final VoidCallback? onTap; - final Color color; - final double radius; + final Color? color; + final double? radius; final Widget child; @override Widget build(BuildContext context) { - Widget child = Padding(padding: padding, child: this.child); + Widget child = Padding( + padding: innerPadding ?? EdgeInsets.all(PharMeTheme.smallToMediumSpace), + child: this.child, + ); if (onTap != null) child = InkWell(onTap: onTap, child: child); // ignore: sized_box_for_whitespace return Container( width: double.infinity, - child: DecoratedBox( - decoration: BoxDecoration( - color: color, - border: Border.all(width: 0.5, color: PharMeTheme.borderColor), - borderRadius: BorderRadius.all(Radius.circular(radius)), - boxShadow: [ - BoxShadow( - color: PharMeTheme.onSurfaceColor, - blurRadius: 16, - offset: Offset(0, 4), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: outerVerticalPadding ?? PharMeTheme.smallSpace * 0.65, + horizontal: outerHorizontalPadding ?? PharMeTheme.smallSpace, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: color ?? darkenColor(PharMeTheme.onSurfaceColor, -0.05), + borderRadius: BorderRadius.all( + Radius.circular(radius ?? PharMeTheme.outerCardRadius) ), - ], + ), + child: child, ), - child: child, ), ); } diff --git a/app/lib/common/widgets/scroll_list.dart b/app/lib/common/widgets/scroll_list.dart index 28796bce6..d96425a0e 100644 --- a/app/lib/common/widgets/scroll_list.dart +++ b/app/lib/common/widgets/scroll_list.dart @@ -2,7 +2,10 @@ import 'package:flutter_list_view/flutter_list_view.dart'; import '../module.dart'; -Widget scrollList(List body, {bool keepPosition = false}) { +Widget scrollList(List body, { + bool keepPosition = false, + double? verticalPadding, +}) { String getItemKey(Widget widget) => widget.key.toString(); if (body.map(getItemKey).toSet().length != body.length) { throw Exception('Items passed to scrollList need unique keys'); @@ -12,10 +15,24 @@ Widget scrollList(List body, {bool keepPosition = false}) { thumbVisibility: true, thickness: PharMeTheme.smallSpace / 2, child: Padding( - padding: EdgeInsets.only(right: PharMeTheme.smallSpace * 1.5), + padding: EdgeInsets.only(right: PharMeTheme.mediumSpace), child: FlutterListView( delegate: FlutterListViewDelegate( - (context, index) => body[index], + (context, index) => (index == 0) + ? Padding( + padding: EdgeInsets.only( + top: verticalPadding ?? PharMeTheme.smallSpace, + ), + child: body[index] + ) + : (index == body.length - 1) + ? Padding( + padding: EdgeInsets.only( + bottom: verticalPadding ?? PharMeTheme.smallSpace, + ), + child: body[index] + ) + : body[index], childCount: body.length, onItemKey: (index) => getItemKey(body[index]), keepPosition: keepPosition, diff --git a/app/lib/common/widgets/subheader_divider.dart b/app/lib/common/widgets/subheader_divider.dart index bddbaee93..c908bc80a 100644 --- a/app/lib/common/widgets/subheader_divider.dart +++ b/app/lib/common/widgets/subheader_divider.dart @@ -1,16 +1,18 @@ import '../module.dart'; class SubheaderDivider extends StatelessWidget { - const SubheaderDivider( - this.text, { + const SubheaderDivider({ + this.text = '', this.indent = 20.0, this.color, - Key? key, - }) : super(key: key); + this.useLine = true, + super.key, + }); final String text; final double indent; final Color? color; + final bool useLine; @override Widget build(BuildContext context) { @@ -20,7 +22,7 @@ class SubheaderDivider extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Divider(color: widgetColor), + if (useLine) Divider(color: widgetColor, thickness: 0.5), Text( text, style: diff --git a/app/lib/common/pages/drug/cubit.dart b/app/lib/drug/cubit.dart similarity index 93% rename from app/lib/common/pages/drug/cubit.dart rename to app/lib/drug/cubit.dart index 1a92b593a..a2a39cd3b 100644 --- a/app/lib/common/pages/drug/cubit.dart +++ b/app/lib/drug/cubit.dart @@ -3,8 +3,7 @@ import 'dart:async'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:overlay_dialog/overlay_dialog.dart'; -import '../../module.dart'; -import '../../utilities/pdf_utils.dart'; +import '../common/module.dart'; part 'cubit.freezed.dart'; diff --git a/app/lib/drug/module.dart b/app/lib/drug/module.dart new file mode 100644 index 000000000..ea729c361 --- /dev/null +++ b/app/lib/drug/module.dart @@ -0,0 +1,8 @@ +import '../common/module.dart'; + +// For generated routes +export 'cubit.dart'; +export 'pages/drug.dart'; + +// Used by multiple parent routes, therefore returning function for creation +AutoRoute drugRoute() => AutoRoute(page: DrugRoute.page); \ No newline at end of file diff --git a/app/lib/common/pages/drug/drug.dart b/app/lib/drug/pages/drug.dart similarity index 81% rename from app/lib/common/pages/drug/drug.dart rename to app/lib/drug/pages/drug.dart index 1ffed21d2..07cbad2fd 100644 --- a/app/lib/common/pages/drug/drug.dart +++ b/app/lib/drug/pages/drug.dart @@ -1,9 +1,10 @@ import 'package:provider/provider.dart'; -import '../../module.dart'; -import 'cubit.dart'; -import 'widgets/module.dart'; +import '../../common/module.dart'; +import '../cubit.dart'; +import '../widgets/module.dart'; +@RoutePage() class DrugPage extends StatelessWidget { const DrugPage( this.drug, { @@ -49,25 +50,19 @@ class DrugPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SubHeader(context.l10n.drugs_page_header_drug), - SizedBox(height: 12), DrugAnnotationCard( drug, isActive: drug.isActive, - setActivity: (value) => + setActivity: ({ value }) => context.read().setActivity(drug, value), disabled: loading, ), - SizedBox(height: 20), + SizedBox(height: PharMeTheme.mediumSpace), SubHeader( context.l10n.drugs_page_header_guideline, tooltip: context.l10n.drugs_page_tooltip_guideline, ), - SizedBox(height: 12), - if (drug.userGuideline != null) ...[ - Disclaimer(), - SizedBox(height: 12), - ], + SizedBox(height: PharMeTheme.smallSpace), GuidelineAnnotationCard(drug), ], ), diff --git a/app/lib/drug/widgets/annotation_cards/annotation_table.dart b/app/lib/drug/widgets/annotation_cards/annotation_table.dart new file mode 100644 index 000000000..82835f7fc --- /dev/null +++ b/app/lib/drug/widgets/annotation_cards/annotation_table.dart @@ -0,0 +1,42 @@ +import '../../../common/module.dart'; + +class TableRowDefinition { + const TableRowDefinition(this.key, this.value); + final String key; + final String value; +} + +Table buildTable( + List rowDefinitions, + { + TextStyle? style, + } +) { + return Table( + defaultColumnWidth: IntrinsicColumnWidth(), + children: rowDefinitions.map((rowDefinition) => _buildRow( + rowDefinition.key, + rowDefinition.value, + style ?? PharMeTheme.textTheme.bodyMedium!, + )).toList(), + ); +} + +TableRow _buildRow( + String key, + String value, + TextStyle textStyle, +) { + return TableRow( + children: [ + Padding( + padding: EdgeInsets.only(right: PharMeTheme.smallSpace), + child: Text( + key, + style: textStyle.copyWith(fontWeight: FontWeight.bold), + ), + ), + Text(value, style: textStyle), + ], + ); +} \ No newline at end of file diff --git a/app/lib/common/pages/drug/widgets/disclaimer.dart b/app/lib/drug/widgets/annotation_cards/disclaimer.dart similarity index 66% rename from app/lib/common/pages/drug/widgets/disclaimer.dart rename to app/lib/drug/widgets/annotation_cards/disclaimer.dart index bff430eab..c463fb26e 100644 --- a/app/lib/common/pages/drug/widgets/disclaimer.dart +++ b/app/lib/drug/widgets/annotation_cards/disclaimer.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - -import '../../../l10n.dart'; -import '../../../theme.dart'; +import '../../../common/module.dart'; class Disclaimer extends StatelessWidget { const Disclaimer({this.text}); @@ -11,24 +8,26 @@ class Disclaimer extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.all(4), + padding: EdgeInsets.all(PharMeTheme.smallSpace), decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8)), + borderRadius: BorderRadius.all( + Radius.circular(PharMeTheme.innerCardRadius * 0.75) + ), color: PharMeTheme.surfaceColor, border: Border.all(color: PharMeTheme.errorColor, width: 1.2), ), child: Row(children: [ Icon( Icons.warning_rounded, - size: 52, + size: PharMeTheme.largeSpace, color: PharMeTheme.errorColor, ), - SizedBox(width: 8), + SizedBox(width: PharMeTheme.smallSpace), Flexible( child: Text( text ?? context.l10n.drugs_page_disclaimer, style: PharMeTheme.textTheme.labelMedium!.copyWith( - fontWeight: FontWeight.w100, + fontWeight: FontWeight.w300, ), ), ), diff --git a/app/lib/drug/widgets/annotation_cards/drug.dart b/app/lib/drug/widgets/annotation_cards/drug.dart new file mode 100644 index 000000000..6dfbac36e --- /dev/null +++ b/app/lib/drug/widgets/annotation_cards/drug.dart @@ -0,0 +1,123 @@ +import '../../../common/module.dart'; +import '../sub_header.dart'; +import 'annotation_table.dart'; + +class DrugAnnotationCard extends StatelessWidget { + const DrugAnnotationCard( + this.drug, { + required this.isActive, + required this.setActivity, + this.disabled = false, + }); + + final Drug drug; + final bool isActive; + final void Function({ bool? value }) setActivity; + final bool disabled; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SubHeader(context.l10n.drugs_page_header_drug), + SizedBox(height: PharMeTheme.smallSpace), + RoundedCard( + innerPadding: EdgeInsets.all(PharMeTheme.mediumSpace), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(drug.annotations.indication), + SizedBox(height: PharMeTheme.smallSpace), + buildTable([ + TableRowDefinition( + context.l10n.drugs_page_header_drugclass, + drug.annotations.drugclass, + ), + if (drug.annotations.brandNames.isNotEmpty) + TableRowDefinition( + context.l10n.drugs_page_header_synonyms, + drug.annotations.brandNames.join(', '), + ), + ]), + if (isInhibitor(drug.name)) ...[ + SizedBox(height: 8), + Text(context.l10n.drugs_page_is_inhibitor( + drug.name, + inhibitedGenes(drug).join(', '), + )), + ], + ], + ), + ), + ), + SizedBox(height: PharMeTheme.mediumSpace), + SubHeader(context.l10n.drugs_page_header_active), + SizedBox(height: PharMeTheme.smallSpace), + RoundedCard( + innerPadding: EdgeInsets.all(PharMeTheme.smallSpace), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: PharMeTheme.smallSpace), + child: DropdownButton( + value: isActive, + isExpanded: true, + icon: const Icon(Icons.expand_more), + onChanged: disabled ? null : (newValue) => { + if (isInhibitor(drug.name)) { + showAdaptiveDialog( + context: context, + builder: (context) => DialogWrapper( + title: context.l10n.drugs_page_active_warn_header, + content: DialogContentText( + context.l10n.drugs_page_active_warn, + ), + actions: [ + DialogAction( + onPressed: () => Navigator.pop(context, 'Cancel'), + text: context.l10n.action_cancel, + ), + DialogAction( + onPressed: () { + Navigator.pop(context, 'OK'); + setActivity(value: newValue); + }, + text: context.l10n.action_continue, + isDestructive: true, + ), + ], + ), + ) + } else { + setActivity(value: newValue) + } + }, + items: [ + DropdownMenuItem( + value: true, + child: Row( + children: [ + Icon( + Icons.check_circle_outline, + color: PharMeTheme.iconColor, + ), + SizedBox(width: PharMeTheme.smallSpace), + Text(context.l10n.drugs_page_active), + ]), + ), + DropdownMenuItem( + value: false, + child: Row( + children: [ + Icon(Icons.cancel_outlined, color: PharMeTheme.iconColor), + SizedBox(width: PharMeTheme.smallSpace), + Text(context.l10n.drugs_page_inactive), + ]), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/app/lib/drug/widgets/annotation_cards/guideline.dart b/app/lib/drug/widgets/annotation_cards/guideline.dart new file mode 100644 index 000000000..3f891be17 --- /dev/null +++ b/app/lib/drug/widgets/annotation_cards/guideline.dart @@ -0,0 +1,136 @@ +import 'package:url_launcher/url_launcher.dart'; + +import '../../../common/module.dart'; +import '../module.dart'; + +class GuidelineAnnotationCard extends StatelessWidget { + const GuidelineAnnotationCard(this.drug); + + final Drug drug; + + @override + Widget build(BuildContext context) { + return RoundedCard( + innerPadding: const EdgeInsets.all(PharMeTheme.mediumSpace), + child: SingleChildScrollView( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (drug.guidelines.isNotEmpty) ...[ + _buildHeader(context), + SizedBox(height: PharMeTheme.mediumSpace), + _buildCard(context), + SizedBox(height: PharMeTheme.mediumSpace), + _buildSourcesSection(context), + ] + else ...[ + _buildHeader(context), + SizedBox(height: PharMeTheme.smallSpace), + _buildCard(context), + ], + ]), + ), + ); + } + + Widget _buildCard(BuildContext context) { + final upperCardText = drug.userGuideline?.annotations.implication ?? + context.l10n.drugs_page_no_guidelines_for_phenotype_implication( + drug.name + ); + final lowerCardText = drug.userGuideline?.annotations.recommendation ?? + context.l10n.drugs_page_no_guidelines_for_phenotype_recommendation; + return RoundedCard( + key: Key('annotationCard'), + radius: PharMeTheme.innerCardRadius, + outerHorizontalPadding: 0, + outerVerticalPadding: 0, + color: drug.warningLevel.color, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Icon( + drug.warningLevel.icon, + color: PharMeTheme.onSurfaceText, + size: PharMeTheme.largeSpace, + ), + SizedBox(width: PharMeTheme.smallToMediumSpace), + Flexible( + child: Text( + upperCardText, + style: PharMeTheme.textTheme.bodyMedium, + ), + ) + ]), + SizedBox(height: PharMeTheme.smallToMediumSpace), + Text( + lowerCardText, + style: PharMeTheme.textTheme.bodyMedium, + ), + if (drug.userGuideline != null) ...[ + SizedBox(height: PharMeTheme.smallToMediumSpace), + Disclaimer(), + ], + ] + ) + ); + } + + Widget _buildHeader(BuildContext context) { + if (drug.userGuideline == null && drug.guidelines.isEmpty) { + return Text( + context.l10n.drugs_page_guidelines_empty(drug.name), + style: TextStyle(fontStyle: FontStyle.italic), + ); + } else { + final genes = drug.userGuideline?.lookupkey.keys ?? + drug.guidelines.first.lookupkey.keys; + final geneDescriptions = genes.map((geneSymbol) { + final phenotypeInformation = UserData.phenotypeFor( + geneSymbol, + context, + drug: drug.name, + ); + var description = phenotypeInformation.phenotype; + if (phenotypeInformation.adaptionText.isNotNullOrBlank) { + description = '$description (${phenotypeInformation.adaptionText})'; + } + return TableRowDefinition(geneSymbol, description); + }); + return buildTable(geneDescriptions.toList()); + } + } + + Widget _buildSourcesSection(BuildContext context) { + // pipes are illegal characters in URLs so please + // - forgive the cheap hack or + // - refactor by making a custom object and defining equality for it :) + final guideline = drug.userGuideline ?? drug.guidelines.first; + final sources = guideline.externalData + .map((data) => '${data.source}|${data.guidelineUrl}') + .toSet(); + return Column(children: [ + ...sources.map( + (source) => GestureDetector( + onTap: () => _launchUrl(Uri.parse(source.split('|')[1])), + child: RoundedCard( + radius: PharMeTheme.innerCardRadius, + outerHorizontalPadding: 0, + outerVerticalPadding: 0, + color: darkenColor(PharMeTheme.onSurfaceColor, 0.05), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Flexible( + child: Text(context.l10n + .drugs_page_sources_description(source.split('|')[0])), + ), + Icon(Icons.chevron_right_rounded) + ]) + ), + ), + ), + ]); + } +} + +Future _launchUrl(Uri url) async { + if (!await launchUrl(url)) throw Error(); +} diff --git a/app/lib/drug/widgets/module.dart b/app/lib/drug/widgets/module.dart new file mode 100644 index 000000000..c79aef87d --- /dev/null +++ b/app/lib/drug/widgets/module.dart @@ -0,0 +1,6 @@ +export 'annotation_cards/annotation_table.dart'; +export 'annotation_cards/disclaimer.dart'; +export 'annotation_cards/drug.dart'; +export 'annotation_cards/guideline.dart'; +export 'sub_header.dart'; +export 'tooltip_icon.dart'; diff --git a/app/lib/common/pages/drug/widgets/source_card.dart b/app/lib/drug/widgets/source_card.dart similarity index 91% rename from app/lib/common/pages/drug/widgets/source_card.dart rename to app/lib/drug/widgets/source_card.dart index e2d284756..cf82036a5 100644 --- a/app/lib/common/pages/drug/widgets/source_card.dart +++ b/app/lib/drug/widgets/source_card.dart @@ -1,7 +1,5 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; +import '../../common/module.dart'; -import '../../../theme.dart'; class SourceCard extends StatelessWidget { const SourceCard({ diff --git a/app/lib/common/pages/drug/widgets/sub_header.dart b/app/lib/drug/widgets/sub_header.dart similarity index 84% rename from app/lib/common/pages/drug/widgets/sub_header.dart rename to app/lib/drug/widgets/sub_header.dart index 5f589f960..ea3913bb4 100644 --- a/app/lib/common/pages/drug/widgets/sub_header.dart +++ b/app/lib/drug/widgets/sub_header.dart @@ -1,7 +1,4 @@ -import 'package:dartx/dartx.dart'; -import 'package:flutter/material.dart'; - -import '../../../theme.dart'; +import '../../common/module.dart'; import 'tooltip_icon.dart'; class SubHeader extends StatelessWidget { diff --git a/app/lib/common/pages/drug/widgets/tooltip_icon.dart b/app/lib/drug/widgets/tooltip_icon.dart similarity index 100% rename from app/lib/common/pages/drug/widgets/tooltip_icon.dart rename to app/lib/drug/widgets/tooltip_icon.dart diff --git a/app/lib/drug_selection/cubit.dart b/app/lib/drug_selection/cubit.dart new file mode 100644 index 000000000..4d0af2c30 --- /dev/null +++ b/app/lib/drug_selection/cubit.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/module.dart'; + +part 'cubit.freezed.dart'; + +class DrugSelectionCubit extends Cubit { + DrugSelectionCubit(this.activeDrugs) : + super(DrugSelectionState.stable()); + + final ActiveDrugs activeDrugs; + + // ignore: avoid_positional_boolean_parameters + Future updateDrugActivity(Drug drug, bool? value) async { + if (value == null) return; + emit(DrugSelectionState.updating()); + await activeDrugs.changeActivity(drug.name, value); + emit(DrugSelectionState.stable()); + } +} + +@freezed +class DrugSelectionState with _$DrugSelectionState { + const factory DrugSelectionState.stable() = _StableState; + const factory DrugSelectionState.updating() = _UpdatingState; +} diff --git a/app/lib/drug_selection/module.dart b/app/lib/drug_selection/module.dart index 2a63e7106..1b00a4161 100644 --- a/app/lib/drug_selection/module.dart +++ b/app/lib/drug_selection/module.dart @@ -1,12 +1,10 @@ import '../common/module.dart'; -import 'pages/drug_selection.dart'; -// We need to expose all pages for AutoRouter -export 'pages/cubit.dart'; +// For generated route +export 'cubit.dart'; export 'pages/drug_selection.dart'; -const drugSelectionRoutes = AutoRoute( - path: 'drugselection', - name: 'DrugSelectionRouter', - page: DrugSelectionPage, -); +AutoRoute drugSelectionRoute() => AutoRoute( + path: '/drugselection', + page: DrugSelectionRoute.page, +); \ No newline at end of file diff --git a/app/lib/drug_selection/pages/cubit.dart b/app/lib/drug_selection/pages/cubit.dart deleted file mode 100644 index 2ae4f0e2d..000000000 --- a/app/lib/drug_selection/pages/cubit.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -import '../../../common/module.dart'; - -part 'cubit.freezed.dart'; - -class DrugSelectionPageCubit extends Cubit { - DrugSelectionPageCubit(this.activeDrugs) : - super(DrugSelectionPageState.stable()); - - final ActiveDrugs activeDrugs; - - // ignore: avoid_positional_boolean_parameters - Future updateDrugActivity(Drug drug, bool? value) async { - if (value == null) return; - emit(DrugSelectionPageState.updating()); - await activeDrugs.changeActivity(drug.name, value); - emit(DrugSelectionPageState.stable()); - } -} - -@freezed -class DrugSelectionPageState with _$DrugSelectionPageState { - const factory DrugSelectionPageState.stable() = _StableState; - const factory DrugSelectionPageState.updating() = _UpdatingState; -} diff --git a/app/lib/drug_selection/pages/drug_selection.dart b/app/lib/drug_selection/pages/drug_selection.dart index d4d07a6fe..ff5b692a5 100644 --- a/app/lib/drug_selection/pages/drug_selection.dart +++ b/app/lib/drug_selection/pages/drug_selection.dart @@ -1,36 +1,36 @@ import 'package:provider/provider.dart'; -import '../../common/models/drug/cached_drugs.dart'; import '../../common/models/metadata.dart'; import '../../common/module.dart' hide MetaData; import '../../common/widgets/drug_list/drug_items/drug_checkbox_list.dart'; import '../../common/widgets/drug_search.dart'; import '../../common/widgets/full_width_button.dart'; -import 'cubit.dart'; +import '../cubit.dart'; +@RoutePage() class DrugSelectionPage extends HookWidget { const DrugSelectionPage({ - Key? key, + super.key, this.concludesOnboarding = true, @visibleForTesting this.cubit, - }) : super(key: key); + }); - final DrugSelectionPageCubit? cubit; + final DrugSelectionCubit? cubit; final bool concludesOnboarding; @override Widget build(BuildContext context) { return Consumer( builder: (context, activeDrugs, child) => BlocProvider( - create: (context) => cubit ?? DrugSelectionPageCubit(activeDrugs), - child: BlocBuilder( + create: (context) => cubit ?? DrugSelectionCubit(activeDrugs), + child: BlocBuilder( builder: (context, state) { return unscrollablePageScaffold( title: context.l10n.drug_selection_header, barBottom: concludesOnboarding ? context.l10n.drug_selection_onboarding_description : null, - padding: PharMeTheme.largeSpace, + padding: PharMeTheme.mediumSpace, body: Column( children: [ Expanded(child: _buildDrugList(context, state)), @@ -44,16 +44,20 @@ class DrugSelectionPage extends HookWidget { ); } - bool _isEditable(DrugSelectionPageState state) { + bool _isEditable(DrugSelectionState state) { return state.when( stable: () => true, updating: () => false ); } - Widget _buildButton(BuildContext context, DrugSelectionPageState state) { + Widget _buildButton(BuildContext context, DrugSelectionState state) { return Padding( - padding: EdgeInsets.only(top: PharMeTheme.mediumSpace), + padding: EdgeInsets.only( + left: PharMeTheme.mediumSpace, + top: PharMeTheme.mediumSpace, + right: PharMeTheme.mediumSpace, + ), child: FullWidthButton( context.l10n.action_continue, () async { @@ -67,7 +71,7 @@ class DrugSelectionPage extends HookWidget { ); } - Widget _buildDrugList(BuildContext context, DrugSelectionPageState state) { + Widget _buildDrugList(BuildContext context, DrugSelectionState state) { if (CachedDrugs.instance.drugs!.isEmpty) { return Column( children: [ @@ -88,7 +92,7 @@ class DrugSelectionPage extends HookWidget { drugItemsBuildParams: { 'checkboxesEnabled': _isEditable(state), 'onCheckboxChange': (drug, value) => context - .read() + .read() .updateDrugActivity(drug, value), }, showDrugInteractionIndicator: false, diff --git a/app/lib/faq/module.dart b/app/lib/faq/module.dart index 804e253c6..38c656133 100644 --- a/app/lib/faq/module.dart +++ b/app/lib/faq/module.dart @@ -1,11 +1,6 @@ import '../common/module.dart'; -import 'pages/faq.dart'; -// We need to expose all pages for AutoRouter +// For generated routes export 'pages/faq.dart'; -const faqRoutes = AutoRoute( - path: 'faq', - name: 'FaqRouter', - page: FaqPage, -); +AutoRoute faqRoute() => AutoRoute(path: 'faq', page: FaqRoute.page); \ No newline at end of file diff --git a/app/lib/faq/pages/faq.dart b/app/lib/faq/pages/faq.dart index cdc7f668b..3905c7eec 100644 --- a/app/lib/faq/pages/faq.dart +++ b/app/lib/faq/pages/faq.dart @@ -1,74 +1,97 @@ import '../../common/module.dart'; import '../constants.dart'; +@RoutePage() class FaqPage extends StatelessWidget { - const FaqPage({Key? key}) : super(key: key); + const FaqPage({super.key}); @override Widget build(BuildContext context) { - return WillPopScope( + return PopScope( + canPop: false, child: pageScaffold(title: context.l10n.tab_faq, body: [ Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(PharMeTheme.smallSpace), child: Column( key: Key('questionsColumn'), + crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 8), ...faqList.keys.fold>( [], (widgets, topic) => [...widgets, ..._buildTopic(context, topic, faqList[topic]!)] ), - Divider(), - ListTile( + ..._buildTopicHeader( + context.l10n.settings_page_contact_us, + addSpace: true, + ), + _buildQuestionCard( + child: ListTile( title: Text(context.l10n.faq_contact_us), trailing: Icon(Icons.chevron_right_rounded), - onTap: sendEmail) + iconColor: PharMeTheme.iconColor, + onTap: sendEmail + ) + ) + ], ), ), ]), - onWillPop: () async => false, ); } + List _buildTopicHeader(String title, { required bool addSpace }) => [ + if (addSpace) SizedBox(height: PharMeTheme.mediumSpace), + SubheaderDivider(text: title, useLine: false), + ]; + + Widget _buildQuestionCard({ required Widget child, Key? key }) => RoundedCard( + key: key, + innerPadding: EdgeInsets.all(PharMeTheme.smallSpace * 0.25), + child: child, + ); + List _buildTopic( BuildContext context, String topicName, List questions ) { + final topicIndex = faqList.keys.toList().indexOf(topicName); return [ - ListTile( - title: Text( - topicName, - style: PharMeTheme.textTheme.bodyMedium, - ), - dense: true, - ), - ...questions.map((question) => _buildQuestion(context, question)).toList() + ..._buildTopicHeader(topicName, addSpace: topicIndex != 0), + ...questions.map((question) => _buildQuestion(context, question)) ]; } Widget _buildQuestion(BuildContext context, Question question) { final key = GlobalKey(); - - return Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: ExpansionTile( - key: key, - title: Text(question.question), - onExpansionChanged: (value) { - if (value) _scrollToSelectedContent(key: key); - }, - children: [ - ListTile( - contentPadding: EdgeInsets.only(left: 16, right: 16, bottom: 8), - title: Text(question.answer), + return _buildQuestionCard( + key: key, + child: Theme( + data: Theme.of(context).copyWith( + dividerColor: Colors.transparent, + ), + child: ExpansionTile( + title: Text(question.question), + iconColor: PharMeTheme.iconColor, + collapsedIconColor: PharMeTheme.iconColor, + onExpansionChanged: (value) { + if (value) _scrollToSelectedContent(key: key); + }, + children: [ + ListTile( + contentPadding: EdgeInsets.only( + left: PharMeTheme.mediumSpace, + right: PharMeTheme.mediumSpace, + bottom: PharMeTheme.smallSpace, + ), + title: Text(question.answer), + ), + ], + ), ), - ], - ), - ); + ); } void _scrollToSelectedContent({required GlobalKey key}) { @@ -78,6 +101,7 @@ class FaqPage extends StatelessWidget { Scrollable.ensureVisible( keyContext, duration: Duration(milliseconds: 200), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, ); }); } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 990f60140..df557a230 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -23,6 +23,7 @@ "update_warning_title": "Updated guidelines", "update_warning_body": "The guidelines for gene-drug interactions were updated. Please review your information, especially for medications you are currently taking.", + "faq_contact_us": "Do you have unanswered questions or feedback? Contact us", "general_continue": "Continue", @@ -150,7 +151,6 @@ } } }, - "drugs_page_your_genome": "Your genome", "drugs_page_guidelines_empty": "No guidelines are present for {drugName}", "@drugs_page_guidelines_empty": { "description": "Disclaimer when a drug does not have guidelines", @@ -161,16 +161,15 @@ } } }, - "drugs_page_header_further_info": "Further information", "drugs_page_header_synonyms": "Other names", "drugs_page_header_drugclass": "Drug class", - "drugs_page_header_drug": "Drug", - "drugs_page_header_druginfo": "Information", + "drugs_page_header_drug": "Drug information", "drugs_page_header_active": "Usage status", - "drugs_page_active": "I am currently taking this drug.", + "drugs_page_active": "I am currently taking this drug", + "drugs_page_inactive": "I am not taking this drug", "drugs_page_active_warn_header": "Are you sure you want to change the drug usage status?", "drugs_page_active_warn": "This can influence your results for other drugs.", - "drugs_page_header_guideline": "Clinical Guideline", + "drugs_page_header_guideline": "DNA-based clinical guideline", "drugs_page_no_guidelines_for_phenotype_implication": "More information is needed to comment on your DNA's influence on {drugName}.", "@drugs_page_no_guidelines_for_phenotype_implication": { "description": "Disclaimer for when no guidelines was found for the current drug based on the user's phenotype", @@ -344,7 +343,8 @@ "settings_page_account_settings": "Settings", "settings_page_delete_data": "Delete app data", "settings_page_delete_data_text": "Are you sure that you want to delete all app data? This also includes your genetic data and will reset the app.", - "settings_page_delete_data_confirmation": "I understand that my genetic data will be deleted and it might not be possible to import it again.", + "settings_page_delete_data_additional_text": "Your genetic data will be deleted and it might not be possible to import it again.", + "settings_page_delete_data_confirmation": "I understand the consequences and want to delete all app data", "settings_page_more": "More", "settings_page_onboarding": "Onboarding", "settings_page_about_us": "About us", @@ -353,6 +353,7 @@ "settings_page_privacy_policy_text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "settings_page_terms_and_conditions": "Terms of use", "settings_page_terms_and_conditions_text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "settings_page_help_and_feedback": "Help & Feedback", "settings_page_contact_us": "Contact us", "comprehension_intro_text": "Would you like to participate in a survey aiming to measure user comprehension of content in the app? This would help us make PharMe more understandable for everyone!", diff --git a/app/lib/login/pages/cubit.dart b/app/lib/login/cubit.dart similarity index 76% rename from app/lib/login/pages/cubit.dart rename to app/lib/login/cubit.dart index b360f7732..b7f806c8f 100644 --- a/app/lib/login/pages/cubit.dart +++ b/app/lib/login/cubit.dart @@ -5,22 +5,22 @@ import 'package:flutter_web_auth/flutter_web_auth.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:http/http.dart' as http; -import '../../../common/module.dart'; -import '../models/lab.dart'; +import '../../common/module.dart'; +import 'models/lab.dart'; part 'cubit.freezed.dart'; -class LoginPageCubit extends Cubit { - LoginPageCubit(this.activeDrugs): super(LoginPageState.initial()); +class LoginCubit extends Cubit { + LoginCubit(this.activeDrugs): super(LoginState.initial()); ActiveDrugs activeDrugs; - void revertToInitialState() => emit(LoginPageState.initial()); + void revertToInitialState() => emit(LoginState.initial()); // signInAndLoadUserData authenticates a user with a Lab and fetches their // genomic data from it's endpoint. Future signInAndLoadUserData(BuildContext context, Lab lab) async { - emit(LoginPageState.loadingUserData()); + emit(LoginState.loadingUserData()); // authenticate String? token; @@ -38,7 +38,7 @@ class LoginPageCubit extends Cubit { } if (token == null) { - emit(LoginPageState.error( + emit(LoginState.error( // ignore: use_build_context_synchronously context.l10n.err_could_not_retrieve_access_token, )); @@ -56,10 +56,10 @@ class LoginPageCubit extends Cubit { // login + fetching of data successful MetaData.instance.isLoggedIn = true; await MetaData.save(); - emit(LoginPageState.loadedUserData()); + emit(LoginState.loadedUserData()); } catch (e) { // ignore: use_build_context_synchronously - emit(LoginPageState.error(context.l10n.err_fetch_user_data_failed)); + emit(LoginState.error(context.l10n.err_fetch_user_data_failed)); } } @@ -102,9 +102,9 @@ class LoginPageCubit extends Cubit { } @freezed -class LoginPageState with _$LoginPageState { - const factory LoginPageState.initial() = _InitialState; - const factory LoginPageState.loadingUserData() = _LoadingUserDataState; - const factory LoginPageState.loadedUserData() = _LoadedUserDataState; - const factory LoginPageState.error(String string) = _ErrorState; +class LoginState with _$LoginState { + const factory LoginState.initial() = _InitialState; + const factory LoginState.loadingUserData() = _LoadingUserDataState; + const factory LoginState.loadedUserData() = _LoadedUserDataState; + const factory LoginState.error(String string) = _ErrorState; } diff --git a/app/lib/login/module.dart b/app/lib/login/module.dart index 5fbae01e8..ad844be59 100644 --- a/app/lib/login/module.dart +++ b/app/lib/login/module.dart @@ -1,12 +1,7 @@ import '../common/module.dart'; -import 'pages/login.dart'; -// We need to expose all pages for AutoRouter -export 'pages/cubit.dart'; +// For generated route +export 'cubit.dart'; export 'pages/login.dart'; -const loginRoutes = AutoRoute( - path: 'login', - name: 'LoginRouter', - page: LoginPage, -); +AutoRoute loginRoute() => AutoRoute(path: '/login', page: LoginRoute.page); \ No newline at end of file diff --git a/app/lib/login/pages/login.dart b/app/lib/login/pages/login.dart index 90f5f5deb..30b8ba6d6 100644 --- a/app/lib/login/pages/login.dart +++ b/app/lib/login/pages/login.dart @@ -3,16 +3,17 @@ import 'package:provider/provider.dart'; import '../../../common/module.dart'; import '../../common/widgets/full_width_button.dart'; +import '../cubit.dart'; import '../models/lab.dart'; -import 'cubit.dart'; +@RoutePage() class LoginPage extends HookWidget { const LoginPage({ - Key? key, + super.key, @visibleForTesting this.cubit, - }) : super(key: key); + }); - final LoginPageCubit? cubit; + final LoginCubit? cubit; @override Widget build(BuildContext context) { @@ -20,8 +21,8 @@ class LoginPage extends HookWidget { return Consumer( builder: (context, activeDrugs, child) => BlocProvider( - create: (context) => cubit ?? LoginPageCubit(activeDrugs), - child: BlocBuilder( + create: (context) => cubit ?? LoginCubit(activeDrugs), + child: BlocBuilder( builder: (context, state) { return unscrollablePageScaffold( padding: PharMeTheme.largeSpace, @@ -66,7 +67,7 @@ class LoginPage extends HookWidget { (el) => el.name == dropdownValue.value, ); await context - .read() + .read() .signInAndLoadUserData(context, selectedLab); } @@ -87,7 +88,12 @@ class LoginPage extends HookWidget { DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, - dropdownOverButton: true, + dropdownStyleData: DropdownStyleData( + isOverButton: true, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + ), + ), hint: Text(context.l10n.auth_choose_lab), value: dropdownValue.value, onChanged: (value) { @@ -99,13 +105,12 @@ class LoginPage extends HookWidget { child: Text(lab.name), )) .toList(), - buttonPadding: const EdgeInsets.only(left: 16, right: 16), - buttonDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(32), - border: Border.all(color: PharMeTheme.borderColor), - ), - dropdownDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + buttonStyleData: ButtonStyleData( + padding: const EdgeInsets.only(left: 16, right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + border: Border.all(color: PharMeTheme.borderColor), + ), ), ), ), @@ -117,7 +122,7 @@ class LoginPage extends HookWidget { return _buildColumnWrapper( action: () => overwriteRoutes( context, - nextPage: OnboardingRouter(), + nextPage: OnboardingRoute(), ), actionText: context.l10n.general_continue, children: [ @@ -138,7 +143,7 @@ class LoginPage extends HookWidget { Widget _buildErrorScreen(BuildContext context, String message) { return _buildColumnWrapper( - action: () => context.read().revertToInitialState(), + action: () => context.read().revertToInitialState(), actionText: context.l10n.general_retry, children: [ Icon( diff --git a/app/lib/main.dart b/app/lib/main.dart index 9a4ad8695..bfb574653 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,5 +1,6 @@ import 'package:provider/provider.dart'; +import 'app.dart'; import 'common/module.dart'; Future main() async { diff --git a/app/lib/main/module.dart b/app/lib/main/module.dart new file mode 100644 index 000000000..d3d6986ac --- /dev/null +++ b/app/lib/main/module.dart @@ -0,0 +1,10 @@ +import '../common/module.dart'; + +// For generated route +export 'pages/main.dart'; + +AutoRoute mainRoute({ required List children }) => AutoRoute( + path: '/main', + page: MainRoute.page, + children: children, +); \ No newline at end of file diff --git a/app/lib/common/pages/main/main.dart b/app/lib/main/pages/main.dart similarity index 83% rename from app/lib/common/pages/main/main.dart rename to app/lib/main/pages/main.dart index 904a16851..98db08039 100644 --- a/app/lib/common/pages/main/main.dart +++ b/app/lib/main/pages/main.dart @@ -1,8 +1,4 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; - -import '../../l10n.dart'; -import '../../routing/router.dart'; +import '../../common/module.dart'; class TabRouteDefinition { TabRouteDefinition({ @@ -18,30 +14,31 @@ class TabRouteDefinition { List getTabRoutesDefinition(BuildContext context) { return [ TabRouteDefinition( - pageRouteInfo: ReportRouter(), + pageRouteInfo: ReportRoute(), label: context.l10n.nav_report, icon: Icon(Icons.summarize_rounded), ), TabRouteDefinition( - pageRouteInfo: SearchRouter(), + pageRouteInfo: SearchRoute(), label: context.l10n.nav_drugs, icon: Icon(Icons.medication_rounded), ), TabRouteDefinition( - pageRouteInfo: FaqRouter(), + pageRouteInfo: FaqRoute(), label: context.l10n.nav_faq, icon: Icon(Icons.lightbulb_rounded), ), TabRouteDefinition( - pageRouteInfo: SettingsRouter(), + pageRouteInfo: MoreRoute(), label: context.l10n.nav_more, icon: Icon(Icons.more_horiz_rounded), ), ]; } +@RoutePage() class MainPage extends StatelessWidget { - const MainPage({Key? key}) : super(key: key); + const MainPage({super.key}); @override Widget build(BuildContext context) { diff --git a/app/lib/more/module.dart b/app/lib/more/module.dart new file mode 100644 index 000000000..a18bcc3de --- /dev/null +++ b/app/lib/more/module.dart @@ -0,0 +1,23 @@ +import '../common/module.dart'; + +// For generated routes +export 'pages/about.dart'; +export 'pages/more.dart'; +export 'pages/privacy.dart'; +export 'pages/terms.dart'; + +@RoutePage() +class MoreRootPage extends AutoRouter {} + +AutoRoute aboutRoute() => AutoRoute(path: 'about', page: AboutRoute.page); +AutoRoute privacyRoute() => AutoRoute(path: 'privacy', page: PrivacyRoute.page); +AutoRoute termsRoute() => AutoRoute(path: 'terms', page: TermsRoute.page); + +AutoRoute moreRoute({ required List children }) => AutoRoute( + path: 'more', + page: MoreRootRoute.page, + children: [ + AutoRoute(path: '', page: MoreRoute.page), + ...children, + ], +); \ No newline at end of file diff --git a/app/lib/settings/pages/about_us.dart b/app/lib/more/pages/about.dart similarity index 82% rename from app/lib/settings/pages/about_us.dart rename to app/lib/more/pages/about.dart index e5030e59d..852bac33d 100644 --- a/app/lib/settings/pages/about_us.dart +++ b/app/lib/more/pages/about.dart @@ -1,7 +1,8 @@ import '../../common/module.dart'; -class AboutUsPage extends StatelessWidget { - const AboutUsPage({Key? key}) : super(key: key); +@RoutePage() +class AboutPage extends StatelessWidget { + const AboutPage({super.key}); @override Widget build(BuildContext context) { diff --git a/app/lib/more/pages/more.dart b/app/lib/more/pages/more.dart new file mode 100644 index 000000000..b5962d1fc --- /dev/null +++ b/app/lib/more/pages/more.dart @@ -0,0 +1,130 @@ +import '../../common/module.dart'; +import '../utils.dart'; + +@RoutePage() +class MorePage extends StatelessWidget { + const MorePage({super.key}); + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + child: pageScaffold(title: context.l10n.tab_more, body: [ + SubheaderDivider( + text: context.l10n.settings_page_account_settings, + useLine: false, + ), + _buildSettingsItem( + title: context.l10n.drug_selection_header, + onTap: () => context.router.push( + DrugSelectionRoute(concludesOnboarding: false) + ), + ), + _buildSettingsItem( + title: context.l10n.settings_page_delete_data, + onTap: () => showDialog( + context: context, + builder: (_) => DeleteDataDialog(), + ), + ), + SubheaderDivider( + text: context.l10n.settings_page_more, + useLine: false, + ), + _buildSettingsItem( + title: context.l10n.settings_page_onboarding, + onTap: () => context.router.push(OnboardingRoute(isRevisiting: true)), + ), + _buildSettingsItem( + title: context.l10n.settings_page_about_us, + onTap: () => context.router.push(AboutRoute()), + ), + _buildSettingsItem( + title: context.l10n.settings_page_privacy_policy, + onTap: () => context.router.push(PrivacyRoute()), + ), + _buildSettingsItem( + title: context.l10n.settings_page_terms_and_conditions, + onTap: () => context.router.push(TermsRoute()), + ), + SubheaderDivider( + text: context.l10n.settings_page_help_and_feedback, + useLine: false, + ), + _buildSettingsItem( + title: context.l10n.settings_page_contact_us, + onTap: sendEmail) + ]), + ); + } + + Widget _buildSettingsItem({ + required String title, + required void Function() onTap, + }) => ListTile( + title: Text(title), + trailing: Icon(Icons.chevron_right_rounded), + iconColor: PharMeTheme.iconColor, + onTap: onTap, + ); +} + +class DeleteDataDialog extends HookWidget { + @override + Widget build(BuildContext context) { + final agreedToDeletion = useState(false); + + return DialogWrapper( + title: context.l10n.settings_page_delete_data, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DialogContentText(context.l10n.settings_page_delete_data_text), + SizedBox(height: PharMeTheme.mediumSpace), + DialogContentText( + context.l10n.settings_page_delete_data_additional_text, + ), + SizedBox(height: PharMeTheme.mediumSpace), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: PharMeTheme.mediumToLargeSpace, + height: PharMeTheme.mediumToLargeSpace, + child: CheckboxWrapper( + isChecked: agreedToDeletion.value, + onChanged: (value) => + agreedToDeletion.value = value ?? agreedToDeletion.value, + ), + ), + SizedBox(width: PharMeTheme.smallSpace), + Expanded( + child: DialogContentText( + context.l10n.settings_page_delete_data_confirmation, + ), + ), + ], + ), + ], + ), + actions: [ + DialogAction( + onPressed: context.router.root.pop, + text: context.l10n.action_cancel, + ), + DialogAction( + onPressed: agreedToDeletion.value + ? () async { + await deleteAllAppData(); + // ignore: use_build_context_synchronously + await overwriteRoutes(context, nextPage: LoginRoute()); + } + : null, + text: context.l10n.action_continue, + isDestructive: true, + ), + ], + ); + } +} diff --git a/app/lib/settings/pages/privacy_policy.dart b/app/lib/more/pages/privacy.dart similarity index 80% rename from app/lib/settings/pages/privacy_policy.dart rename to app/lib/more/pages/privacy.dart index 82ff95f57..f5125ec85 100644 --- a/app/lib/settings/pages/privacy_policy.dart +++ b/app/lib/more/pages/privacy.dart @@ -1,7 +1,8 @@ import '../../common/module.dart'; -class PrivacyPolicyPage extends StatelessWidget { - const PrivacyPolicyPage({Key? key}) : super(key: key); +@RoutePage() +class PrivacyPage extends StatelessWidget { + const PrivacyPage({super.key}); @override Widget build(BuildContext context) { diff --git a/app/lib/settings/pages/terms_and_conditions.dart b/app/lib/more/pages/terms.dart similarity index 82% rename from app/lib/settings/pages/terms_and_conditions.dart rename to app/lib/more/pages/terms.dart index 9c1f29d0d..8844f17ea 100644 --- a/app/lib/settings/pages/terms_and_conditions.dart +++ b/app/lib/more/pages/terms.dart @@ -1,7 +1,8 @@ import '../../common/module.dart'; -class TermsAndConditionsPage extends StatelessWidget { - const TermsAndConditionsPage({Key? key}) : super(key: key); +@RoutePage() +class TermsPage extends StatelessWidget { + const TermsPage({super.key}); @override Widget build(BuildContext context) { diff --git a/app/lib/settings/utils.dart b/app/lib/more/utils.dart similarity index 92% rename from app/lib/settings/utils.dart rename to app/lib/more/utils.dart index 8a7efae23..e049ba8f6 100644 --- a/app/lib/settings/utils.dart +++ b/app/lib/more/utils.dart @@ -1,6 +1,5 @@ import 'package:path_provider/path_provider.dart'; -import '../common/models/drug/cached_drugs.dart'; import '../common/module.dart'; Future deleteAllAppData() async { diff --git a/app/lib/onboarding/module.dart b/app/lib/onboarding/module.dart index e01f2332a..1b99465f7 100644 --- a/app/lib/onboarding/module.dart +++ b/app/lib/onboarding/module.dart @@ -1,10 +1,9 @@ import '../common/module.dart'; -import 'pages/onboarding.dart'; +// For generated route export 'pages/onboarding.dart'; -const onboardingRoutes = AutoRoute( - path: 'onboarding', - name: 'OnboardingRouter', - page: OnboardingPage, -); +AutoRoute onboardingRoute() => AutoRoute( + path: '/onboarding', + page: OnboardingRoute.page, +); \ No newline at end of file diff --git a/app/lib/onboarding/pages/onboarding.dart b/app/lib/onboarding/pages/onboarding.dart index 158cdffa4..5763ae8ff 100644 --- a/app/lib/onboarding/pages/onboarding.dart +++ b/app/lib/onboarding/pages/onboarding.dart @@ -1,6 +1,7 @@ import '../../../common/module.dart' hide MetaData; import '../../common/models/metadata.dart'; +@RoutePage() class OnboardingPage extends HookWidget { OnboardingPage({ this.isRevisiting = false }); @@ -95,7 +96,7 @@ class OnboardingPage extends HookWidget { right: PharMeTheme.mediumToLargeSpace, child: IconButton( icon: Icon(Icons.close, size: 32, color: Colors.white,), - onPressed: () => context.navigateBack(), + onPressed: () => context.router.back(), ) ), Positioned( @@ -167,13 +168,13 @@ class OnboardingPage extends HookWidget { onPressed: () async { if (isLastPage) { if (isRevisiting) { - context.router.navigateBack(); + context.router.back(); } else { MetaData.instance.onboardingDone = true; await MetaData.save(); // ignore: use_build_context_synchronously await context.router.push( - DrugSelectionRouter(concludesOnboarding: true) + DrugSelectionRoute(concludesOnboarding: true) ); } } else { diff --git a/app/lib/report/module.dart b/app/lib/report/module.dart index 25a69296c..a2fc0a7bb 100644 --- a/app/lib/report/module.dart +++ b/app/lib/report/module.dart @@ -1,18 +1,19 @@ import '../common/module.dart'; -import '../search/module.dart'; -import 'pages/gene.dart'; -import 'pages/report.dart'; +// For generated routes export 'pages/gene.dart'; export 'pages/report.dart'; -const reportRoutes = AutoRoute( +@RoutePage() +class ReportRootPage extends AutoRouter {} + +AutoRoute geneRoute() => AutoRoute(page: GeneRoute.page); + +AutoRoute reportRoute({ required List children }) => AutoRoute( path: 'report', - name: 'ReportRouter', - page: EmptyRouterPage, + page: ReportRootRoute.page, children: [ - AutoRoute(path: '', page: ReportPage), - AutoRoute(page: GenePage), - AutoRoute(page: DrugPage) + AutoRoute(path: '', page: ReportRoute.page), + ...children, ], ); diff --git a/app/lib/report/pages/gene.dart b/app/lib/report/pages/gene.dart index fae05a03d..f42159fb1 100644 --- a/app/lib/report/pages/gene.dart +++ b/app/lib/report/pages/gene.dart @@ -1,9 +1,9 @@ import 'package:provider/provider.dart'; import '../../common/module.dart'; -import '../../common/pages/drug/widgets/sub_header.dart'; -import '../../common/pages/drug/widgets/tooltip_icon.dart'; +import '../../drug/widgets/module.dart'; +@RoutePage() class GenePage extends HookWidget { GenePage(this.phenotype) : cubit = DrugListCubit( @@ -37,6 +37,7 @@ class GenePage extends HookWidget { ), SizedBox(height: PharMeTheme.smallToMediumSpace), RoundedCard( + radius: PharMeTheme.mediumSpace, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/app/lib/report/pages/report.dart b/app/lib/report/pages/report.dart index 84524a03a..9579f8e26 100644 --- a/app/lib/report/pages/report.dart +++ b/app/lib/report/pages/report.dart @@ -1,9 +1,8 @@ import 'package:provider/provider.dart'; -import '../../common/models/drug/cached_drugs.dart'; import '../../common/module.dart'; -import '../../common/utilities/color_utils.dart'; +@RoutePage() class ReportPage extends StatelessWidget { @override Widget build(BuildContext context) { @@ -25,45 +24,37 @@ class ReportPage extends StatelessWidget { lookupkey: notTestedString ) ); - return WillPopScope( + return PopScope( + canPop: false, child: unscrollablePageScaffold( title: context.l10n.tab_report, barBottom: context.l10n.report_content_explanation, body: Column( children: [ + if (hasActiveInhibitors) Padding( + padding: EdgeInsets.only(top: PharMeTheme.smallSpace), + child: PageIndicatorExplanation( + context.l10n.report_page_indicator_explanation( + drugInteractionIndicatorName, + drugInteractionIndicator + ), + ), + ), scrollList( - userPhenotypes.map((phenotype) => - Column( - key: Key('gene-card-${phenotype.geneSymbol}'), - children: [ - GeneCard(phenotype), - SizedBox(height: 8) - ] - ) - ).toList()), - if (hasActiveInhibitors) drugInteractionExplanation(context), + userPhenotypes.map((phenotype) => GeneCard( + phenotype, + key: Key('gene-card-${phenotype.geneSymbol}') + )).toList(), + ), ] ) ), - onWillPop: () async => false, ); } - - Widget drugInteractionExplanation(BuildContext context) { - return Column(children: [ - SizedBox(height: PharMeTheme.smallSpace), - Text( - context.l10n.report_page_indicator_explanation( - drugInteractionIndicatorName, - drugInteractionIndicator - ) - ), - ]); - } } class GeneCard extends StatelessWidget { - const GeneCard(this.phenotype); + const GeneCard(this.phenotype, { super.key }); final CpicPhenotype phenotype; @@ -107,7 +98,6 @@ class GeneCard extends StatelessWidget { ).toList(); return RoundedCard( onTap: () => context.router.push(GeneRoute(phenotype: phenotype)), - padding: EdgeInsets.all(8), radius: 16, child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( diff --git a/app/lib/search/module.dart b/app/lib/search/module.dart index 09f1e070c..c98075765 100644 --- a/app/lib/search/module.dart +++ b/app/lib/search/module.dart @@ -1,19 +1,16 @@ -import 'package:auto_route/auto_route.dart'; +import '../common/module.dart'; -import '../common/pages/drug/drug.dart'; -import 'pages/search.dart'; - -export '../common/models/module.dart'; -export '../common/pages/drug/cubit.dart'; -export '../common/pages/drug/drug.dart'; +// For generated routes export 'pages/search.dart'; -const searchRoutes = AutoRoute( +@RoutePage() +class SearchRootPage extends AutoRouter {} + +AutoRoute searchRoute({ required List children }) => AutoRoute( path: 'search', - name: 'SearchRouter', - page: EmptyRouterPage, + page: SearchRootRoute.page, children: [ - AutoRoute(path: '', page: SearchPage), - AutoRoute(page: DrugPage), + AutoRoute(path: '', page: SearchRoute.page), + ...children, ], -); +); \ No newline at end of file diff --git a/app/lib/search/pages/search.dart b/app/lib/search/pages/search.dart index e7aeb1c97..7ba0bed0a 100644 --- a/app/lib/search/pages/search.dart +++ b/app/lib/search/pages/search.dart @@ -2,12 +2,12 @@ import '../../../common/module.dart'; import '../../common/widgets/drug_list/drug_items/drug_cards.dart'; import '../../common/widgets/drug_search.dart'; +@RoutePage() class SearchPage extends HookWidget { SearchPage({ - Key? key, + super.key, @visibleForTesting DrugListCubit? cubit, - }) : cubit = cubit ?? DrugListCubit(), - super(key: key); + }) : cubit = cubit ?? DrugListCubit(); final DrugListCubit cubit; @@ -18,7 +18,8 @@ class SearchPage extends HookWidget { await cubit.loadDrugs(useCache: false); } }); - return WillPopScope( + return PopScope( + canPop: false, child: unscrollablePageScaffold( title: context.l10n.tab_drugs, body: DrugSearch( @@ -28,7 +29,6 @@ class SearchPage extends HookWidget { showDrugInteractionIndicator: true, ), ), - onWillPop: () async => false, ); } } diff --git a/app/lib/settings/module.dart b/app/lib/settings/module.dart deleted file mode 100644 index 8fad25f3b..000000000 --- a/app/lib/settings/module.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:auto_route/auto_route.dart'; - -import '../common/module.dart'; - -import 'pages/about_us.dart'; -import 'pages/privacy_policy.dart'; -import 'pages/settings.dart'; -import 'pages/terms_and_conditions.dart'; - -// We need to expose all pages for AutoRouter -export 'pages/about_us.dart'; -export 'pages/privacy_policy.dart'; -export 'pages/settings.dart'; -export 'pages/terms_and_conditions.dart'; - -const settingsRoutes = AutoRoute( - path: 'settings', - name: 'SettingsRouter', - page: EmptyRouterPage, - children: [ - AutoRoute(path: '', page: SettingsPage), - AutoRoute(path: 'about_us', page: AboutUsPage), - AutoRoute(path: 'privacy_policy', page: PrivacyPolicyPage), - AutoRoute(path: 'terms_and_conditions', page: TermsAndConditionsPage), - ], -); diff --git a/app/lib/settings/pages/settings.dart b/app/lib/settings/pages/settings.dart deleted file mode 100644 index 3c522d87a..000000000 --- a/app/lib/settings/pages/settings.dart +++ /dev/null @@ -1,114 +0,0 @@ -import '../../common/module.dart'; -import '../../common/pages/drug/widgets/adaptive_dialog.dart'; -import '../utils.dart'; - -class SettingsPage extends StatelessWidget { - const SettingsPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return WillPopScope( - child: pageScaffold(title: context.l10n.tab_more, body: [ - ListTile( - title: Text( - context.l10n.settings_page_account_settings, - style: PharMeTheme.textTheme.bodyMedium, - ), - dense: true, - ), - ListTile( - title: Text(context.l10n.drug_selection_header), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => context.router.push( - DrugSelectionRouter(concludesOnboarding: false) - ), - ), - ListTile( - title: Text(context.l10n.settings_page_delete_data), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => showDialog( - context: context, - builder: (_) => DeleteDataDialog(), - ), - ), - Divider(), - ListTile( - title: Text( - context.l10n.settings_page_more, - style: PharMeTheme.textTheme.bodyMedium, - ), - dense: true, - ), - ListTile( - title: Text(context.l10n.settings_page_onboarding), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => context.router.push(OnboardingRouter(isRevisiting: true)), - ), - ListTile( - title: Text(context.l10n.settings_page_about_us), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => context.router.push(AboutUsRoute()), - ), - ListTile( - title: Text(context.l10n.settings_page_privacy_policy), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => context.router.push(PrivacyPolicyRoute()), - ), - ListTile( - title: Text(context.l10n.settings_page_terms_and_conditions), - trailing: Icon(Icons.chevron_right_rounded), - onTap: () => context.router.push(TermsAndConditionsRoute()), - ), - Divider(), - ListTile( - title: Text(context.l10n.settings_page_contact_us), - trailing: Icon(Icons.chevron_right_rounded), - onTap: sendEmail) - ]), - onWillPop: () async => false, - ); - } -} - -class DeleteDataDialog extends HookWidget { - @override - Widget build(BuildContext context) { - final agreedToDeletion = useState(false); - - return AdaptiveAlertDialog( - title: context.l10n.settings_page_delete_data, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(context.l10n.settings_page_delete_data_text), - SizedBox(height: PharMeTheme.mediumSpace), - CheckboxListTile( - value: agreedToDeletion.value, - onChanged: (value) => agreedToDeletion.value = value - ?? agreedToDeletion.value, - title: Text(context.l10n.settings_page_delete_data_confirmation), - controlAffinity: ListTileControlAffinity.leading, - contentPadding: EdgeInsets.zero, - activeColor: PharMeTheme.primaryColor, - ), - ]), - actions: [ - AdaptiveDialogAction( - onPressed: context.router.root.pop, - text: context.l10n.action_cancel, - ), - AdaptiveDialogAction( - isDestructive: true, - onPressed: agreedToDeletion.value - ? () async { - await deleteAllAppData(); - // ignore: use_build_context_synchronously - await overwriteRoutes(context, nextPage: LoginRouter()); - } - : null, - text: context.l10n.action_continue, - ), - ], - ); - } -} diff --git a/app/pubspec.lock b/app/pubspec.lock index 7638491e3..40cf68ec4 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -5,58 +5,58 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: d37dfd404e9bb23adb23ee61fad5b8e14e0ae018fb6948eda6ca44b197ff1158 + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "43.0.0" + version: "64.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "40cbac251e4fffed9c85afca62e1d8236f4778d647934220f38007e2bd9009d8" + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "6.2.0" archive: dependency: transitive description: name: archive - sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.4.9" args: dependency: transitive description: name: args - sha256: b003c3098049a51720352d219b0bb5f219b60fbfb68e7a4748139a06a5676515 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.2" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" auto_route: dependency: "direct main" description: name: auto_route - sha256: "931f7c93fdfff8a0344b885dd2f3001746c01fb05820208fe51ddccc6f081a13" + sha256: "82f8df1d177416bc6b7a449127d0270ff1f0f633a91f2ceb7a85d4f07c3affa1" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "7.8.4" auto_route_generator: dependency: "direct dev" description: name: auto_route_generator - sha256: "3d0239a0d6f4a0a5b9f511b4a46f43812a6f2261887de8f5a0bdfe2a44465073" + sha256: "11067a3bcd643812518fe26c0c9ec073990286cabfd9d74b6da9ef9b913c4d22" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "7.3.2" auto_size_text: dependency: "direct main" description: @@ -69,34 +69,42 @@ packages: dependency: transitive description: name: barcode - sha256: "52570564684bbb0240a9f1fdb6bad12adc5e0540103c1c96d6dd550bd928b1c9" + sha256: "2a8b2ee065f419c2aeda141436cc556d91ae772d220fd80679f4d431d6c2ab43" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.5" + bidi: + dependency: transitive + description: + name: bidi + sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + url: "https://pub.dev" + source: hosted + version: "2.0.10" black_hole_flutter: dependency: "direct main" description: name: black_hole_flutter - sha256: ad957dd07106932f07b040dcc4d08c7532a284821183a21ca5ad993b545c2707 + sha256: d1646e3638c15cd56a0f075128fa50317d26e7c824bde6b981d70118be34bd2f url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "1.1.0" bloc: dependency: "direct main" description: name: bloc - sha256: bd4f8027bfa60d96c8046dec5ce74c463b2c918dce1b0d36593575995344534a + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.2" bloc_test: dependency: "direct dev" description: name: bloc_test - sha256: "622b97678bf8c06a94f4c26a89ee9ebf7319bf775383dee2233e86e1f94ee28d" + sha256: "02f04270be5abae8df171143e61a0058a7acbce5dcac887612e89bb40cca4c33" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.5" boolean_selector: dependency: transitive description: @@ -109,50 +117,50 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: name: build_config - sha256: "5b7355c14258f5e7df24bad1566f7b991de3e54aeacfb94e1a65e5233d9739c1" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "9aae031a54ab0beebc30a888c93e900d15ae2fd8883d031dbfbd5ebdb57f5a4c" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "56942f8114731d1e79942cd981cfef29501937ff1bccf4dbdce0273f31f13640" + sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.7" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "409c20ff6b6a9c9f4152fc9fcbf16440fedf02fcacc0fb26ea3b8eab9a860a40" + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 url: "https://pub.dev" source: hosted - version: "7.2.4" + version: "7.2.11" built_collection: dependency: transitive description: @@ -165,34 +173,34 @@ packages: dependency: transitive description: name: built_value - sha256: d7a9cd57c215bdf8d502772447aa6b52a8ab3f956d25d5fdea6ef1df2d2dad60 + sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "8.8.1" characters: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" - charcode: + version: "1.3.0" + checked_yaml: dependency: transitive description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "1.3.1" - checked_yaml: + version: "2.0.3" + cli_util: dependency: transitive description: - name: checked_yaml - sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659 + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "0.4.1" clock: dependency: transitive description: @@ -205,74 +213,66 @@ packages: dependency: transitive description: name: code_builder - sha256: "02ce3596b459c666530f045ad6f96209474e8fee6e4855940a3cee65fb872ec5" + sha256: feee43a5c05e7b3199bb375a86430b8ada1b04104f2923d0e03cc01ca87b6d84 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.9.0" collection: dependency: "direct main" description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" convert: dependency: transitive description: name: convert - sha256: "196284f26f69444b7f5c50692b55ec25da86d9e500451dc09333bf2e3ad69259" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.1" coverage: dependency: transitive description: name: coverage - sha256: "525ac94733f9ce82507a050bfd62ad89eb1dcbc56308e1e2e17ab11abeee4a75" + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.4" crypto: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.dev" - source: hosted - version: "3.0.2" - csslib: - dependency: transitive - description: - name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "0.17.2" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" dart_style: dependency: transitive description: name: dart_style - sha256: "8aff82f9b26fd868992e5430335a9d773bfef01e1d852d7ba71bf4c5d9349351" + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.4" dartx: dependency: "direct main" description: name: dartx - sha256: "45d7176701f16c5a5e00a4798791c1964bc231491b879369c818dd9a9c764871" + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" data_size: dependency: transitive description: @@ -285,58 +285,26 @@ packages: dependency: "direct main" description: name: debug_overlay - sha256: "33fd675a18cef743454fc15a4f84c3d9e3a6ca3be1b06466746f28b6d791f9fc" + sha256: "3c630f1c4e59dca72e9da9caac8602645c252b78fd9f6802ef5b2b260e40a6b2" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.11" device_info_plus: dependency: transitive description: name: device_info_plus - sha256: c2386729379f04cd39ee0d5d4c48d8c8a0e70f7622dac626cbf5e396392602fd + sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" url: "https://pub.dev" source: hosted - version: "3.2.4" - device_info_plus_linux: - dependency: transitive - description: - name: device_info_plus_linux - sha256: e4eb5db4704f5534e872148a21cfcd39581022b63df556da6720d88f7c9f91a9 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - device_info_plus_macos: - dependency: transitive - description: - name: device_info_plus_macos - sha256: "38871fd2ad31871399d8307630c9f4eb5941dd2c643ee221c44d58de95d367a1" - url: "https://pub.dev" - source: hosted - version: "2.2.3" + version: "9.1.1" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: b2743934f0efc3e291880d76fb341ea114b7e8417d77ee0f93bd21f5dfd3e8d2 + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 url: "https://pub.dev" source: hosted - version: "2.6.1" - device_info_plus_web: - dependency: transitive - description: - name: device_info_plus_web - sha256: "38715ad1ef3bee8915dd7bee08a9ac9ab54472a8df425c887062a3046209f663" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - device_info_plus_windows: - dependency: transitive - description: - name: device_info_plus_windows - sha256: "8fb1403fc94636d6ab48aeebb5f9379f2ca51cde3b337167ec6f39db09234492" - url: "https://pub.dev" - source: hosted - version: "2.1.1" + version: "7.0.0" diff_match_patch: dependency: transitive description: @@ -349,10 +317,10 @@ packages: dependency: "direct main" description: name: dropdown_button2 - sha256: b036a3006b6cdc964488f4eae366ee83b3b33eaa2677f0292538a82fa85201e1 + sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1 url: "https://pub.dev" source: hosted - version: "1.8.5" + version: "2.3.9" fake_async: dependency: transitive description: @@ -365,10 +333,10 @@ packages: dependency: transitive description: name: ffi - sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" file: dependency: transitive description: @@ -381,10 +349,10 @@ packages: dependency: transitive description: name: fixnum - sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -394,10 +362,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "890c51c8007f0182360e523518a0c732efb89876cb4669307af7efada5b55557" + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.3" flutter_driver: dependency: transitive description: flutter @@ -407,26 +375,26 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: "2b202559a4ed3656bbb7aae9d8b335fb0037b23acc7ae3f377d1ba0b95c21aec" + sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" url: "https://pub.dev" source: hosted - version: "0.18.5+1" + version: "0.20.3" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.13.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" flutter_list_view: dependency: "direct main" description: @@ -444,50 +412,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "5abe3d5c25ab435e48c47fb61bac25606062a305fac637c2f020e25abd30014a" + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 url: "https://pub.dev" source: hosted - version: "5.1.2" + version: "9.0.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "7b36defaed0a994cc58d6a169a7c89c695ea660ef86903df13c2a2708fb1e1bb" + sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: cd8eff6ebb4d9083aa3a57f29da00df837a869e8d5c65d9ad1ce549037c55d06 + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "9af003dec5ba9f959300fd76ac8adf5608fbfbc957cdd7a987af206a8e073b32" + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "926716d84eb45220cb1783af629093301613f5e5b51da7c1813e430d69bd716f" + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.2" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: cf9fd49c2807f80dc01f449592e1e7971a3697d1e248b653efb8323536dc2961 + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "3.0.0" flutter_share: dependency: "direct main" description: @@ -500,18 +468,18 @@ packages: dependency: "direct main" description: name: flutter_sliding_up_panel - sha256: "60fdb281d2a81047dadecb931f9b53b6f7189680cd71f898441073207ff90e6f" + sha256: "94f928973d83e146bbc52051e2d9f2a7ed7a5c9e7f04b54d835fff2e41d6cb99" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: f2aa23d9d1721cd5c2c5097558368d2a1f85be35a85a6cf26a626e80c369a47c + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -521,10 +489,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth - sha256: "1becc47ee73009e9ea19402e2194c35dda2b2fbbe2023894df97c8caa4781335" + sha256: a69fa8f43b9e4d86ac72176bf747b735e7b977dd7cf215076d95b87cb05affdd url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.5.0" flutter_web_plugins: dependency: transitive description: flutter @@ -534,26 +502,26 @@ packages: dependency: "direct dev" description: name: freezed - sha256: b8f1017d491344ef41045d3fe85950404c49e74eeaf84a84d7b8b67ac24dfb91 + sha256: "6c5031daae12c7072b3a87eff98983076434b4889ef2a44384d0cae3f82372ba" url: "https://pub.dev" source: hosted - version: "2.1.0+1" + version: "2.4.6" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: "625eb228fd9f00f952b7cd245be34791434fad48375f74e46f97dea4b4e11678" + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.4.1" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "4f4a162323c86ffc1245765cfe138872b8f069deb42f7dbb36115fa27f31469b" + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.2.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -563,26 +531,26 @@ packages: dependency: transitive description: name: glob - sha256: c51b4fdfee4d281f49b8c957f1add91b815473597f76bcf07377987f66a55729 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" google_fonts: dependency: "direct main" description: name: google_fonts - sha256: "8f099045e2f2a30e4d4d0a35f40c6bc941a8f2ca0e10ad9d214ee9edd3f37483" + sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "6.1.0" graphs: dependency: transitive description: name: graphs - sha256: ae0b3d956ff324c6f8671f08dcb2dbd71c99cdbf2aa3ca63a14190c47aa6679c + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.1" hive: dependency: "direct main" description: @@ -603,26 +571,18 @@ packages: dependency: "direct dev" description: name: hive_generator - sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - html: - dependency: transitive - description: - name: html - sha256: bfef906cbd4e78ef49ae511d9074aebd1d2251482ef601a280973e8b58b51bbf + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" url: "https://pub.dev" source: hosted - version: "0.15.0" + version: "2.0.1" http: dependency: "direct main" description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "1.1.2" http_multi_server: dependency: transitive description: @@ -635,26 +595,26 @@ packages: dependency: transitive description: name: http_parser - sha256: db3060f22889f3d9d55f6a217565486737037eec3609f7f3eca4d0c67ee0d8a0 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" image: dependency: transitive description: name: image - sha256: "9d2c5f73435a70a936d317769ee8e7ef480e37674b9f2bce95ea98969a307855" + sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.1.3" implicitly_animated_list: dependency: transitive description: name: implicitly_animated_list - sha256: eb87227f9d55ac2d927ea8c2ef59b6cd24cada87f869a7a584c1c9e07cbf35c6 + sha256: "327074913ed350fbd5089bf433d6268fb98022210e03668035523da7ff64dfdb" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" integration_test: dependency: "direct dev" description: flutter @@ -664,50 +624,58 @@ packages: dependency: "direct main" description: name: intl - sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "0.18.1" io: dependency: transitive description: name: io - sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: cb314f00b2488de7bc575207e54402cd2f92363f333a7933fd1b0631af226baa + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: "0cec7060459254cf1ff980c08dedca6fa50917724a3c3ec8c5026cb88dee8238" + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.7.1" + json_view: + dependency: transitive + description: + name: json_view + sha256: "905c69f9e69d1eab5406b87ab6c10c3706c04c70c6a4959621bd2b43c2d27374" + url: "https://pub.dev" + source: hosted + version: "0.4.2" lints: dependency: transitive description: name: lints - sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" list_diff: dependency: transitive description: @@ -720,50 +688,50 @@ packages: dependency: transitive description: name: logging - sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.10.0" mime: dependency: transitive description: name: mime - sha256: dab22e92b41aa1255ea90ddc4bc2feaf35544fd0728e209638cad041a6e3928a + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" mocktail: dependency: "direct dev" description: name: mocktail - sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + sha256: f603ebd85a576e5914870b02e5839fc5d0243b867bf710651cf239a28ebb365e url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.2" nested: dependency: transitive description: @@ -776,10 +744,10 @@ packages: dependency: transitive description: name: node_preamble - sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" overlay_dialog: dependency: "direct main" description: @@ -800,66 +768,26 @@ packages: dependency: transitive description: name: package_info_plus - sha256: "7a6114becdf042be2b0777d77ace954d4a205644171a1cbd8712976b9bbb837c" - url: "https://pub.dev" - source: hosted - version: "1.4.2" - package_info_plus_linux: - dependency: transitive - description: - name: package_info_plus_linux - sha256: "04b575f44233d30edbb80a94e57cad9107aada334fc02aabb42b6becd13c43fc" - url: "https://pub.dev" - source: hosted - version: "1.0.5" - package_info_plus_macos: - dependency: transitive - description: - name: package_info_plus_macos - sha256: a2ad8b4acf4cd479d4a0afa5a74ea3f5b1c7563b77e52cc32b3ee6956d5482a6 + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "5.0.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f7a0c8f1e7e981bc65f8b64137a53fd3c195b18d429fba960babc59a5a1c7ae8 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - package_info_plus_web: - dependency: transitive - description: - name: package_info_plus_web - sha256: f0829327eb534789e0a16ccac8936a80beed4e2401c4d3a74f3f39094a822d3b + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" url: "https://pub.dev" source: hosted - version: "1.0.6" - package_info_plus_windows: - dependency: transitive - description: - name: package_info_plus_windows - sha256: a6040b8695c82f8dd3c3d4dfab7ef96fbe9c67aac21b9bcf5db272589ef84441 - url: "https://pub.dev" - source: hosted - version: "1.0.5" + version: "2.0.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" - path_drawing: - dependency: transitive - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -872,90 +800,90 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "050e8e85e4b7fecdf2bb3682c1c64c4887a183720c802d323de8a5fd76d372dd" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.1.1" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "4d5542667150f5b779ba411dd5dc0b674a85d1355e45bda2877e0e82f4ad08d8" + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 url: "https://pub.dev" source: hosted - version: "2.0.20" - path_provider_ios: + version: "2.2.1" + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - sha256: "03d639406f5343478352433f00d3c4394d52dac8df3d847869c5e2333e0bbce8" + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.3.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 - url: "https://pub.dev" - source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - sha256: "2a97e7fbb7ae9dcd0dfc1220a78e9ec3e71da691912e617e8715ff2a13086ae8" + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.2.1" pdf: dependency: "direct main" description: name: pdf - sha256: "47bcb9818427d099ccc8bea7608eef8fc0e3a6bfd9c2370565361ea78eabf6bf" + sha256: "93cbb2c06de9bab91844550f19896b2373e7a5ce25173995e7e5ec5e1741429d" url: "https://pub.dev" source: hosted - version: "3.8.3" + version: "3.10.7" petitparser: dependency: transitive description: name: petitparser - sha256: "2ebb289dc4764ec397f5cd3ca9881c6d17196130a7d646ed022a0dd9c2e25a71" + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.7" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" pool: dependency: transitive description: @@ -964,22 +892,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - popover: - dependency: "direct main" - description: - name: popover - sha256: "4255a09e3bb64cada6aebdeeeb15453a2790802d4eecb9256ff5c895863582ea" - url: "https://pub.dev" - source: hosted - version: "0.2.8+1" printing: dependency: "direct main" description: name: printing - sha256: "4df9e22bd4cb2ecea67183e5b328711b10276127b8878d9d5cb60ccd4c24a77c" + sha256: ad39a42a5f83125952457dfd94f395c8cf0eb1f7759583dadb769be5c7f99d24 url: "https://pub.dev" source: hosted - version: "5.9.3" + version: "5.11.1" process: dependency: transitive description: @@ -1000,18 +920,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "816c1a640e952d213ddd223b3e7aafae08cd9f8e1f6864eed304cc13b0272b07" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" qr: dependency: transitive description: @@ -1032,10 +952,10 @@ packages: dependency: transitive description: name: sensors_plus_platform_interface - sha256: dde5184f72b56b25a19d5d88a27154bedf5c3fb9584def621d0d320b613c8d09 + sha256: bc472d6cfd622acb4f020e726433ee31788b038056691ba433fec80e448a094f url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" sensors_plus_web: dependency: transitive description: @@ -1048,106 +968,98 @@ packages: dependency: transitive description: name: shake - sha256: "4c3b9477d72b88e5108a3f13c8cadfa83355bed50b26087166628602f4e261af" + sha256: "107546951c6b8f5e4c2dca66dfb3aa27dd1a853b4e9a26c9aea224b167045023" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "76917b7d4b9526b2ba416808a7eb9fb2863c1a09cf63ec85f1453da240fa818a" + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.2.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3e128864b9cff21bdd5b3ad569953070a851d62901bee880bb052b1110b38007" + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" url: "https://pub.dev" source: hosted - version: "2.0.13" - shared_preferences_ios: + version: "2.2.1" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - sha256: "585a14cefec7da8c9c2fb8cd283a3bb726b4155c0952afe6a0caaa7b2272de34" + name: shared_preferences_foundation + sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "28aefc1261746e7bad3d09799496054beb84e8c4ffcdfed7734e17b4ada459a5" + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - sha256: fbb94bf296576f49be37a1496d5951796211a8db0aa22cc0d68c46440dad808c - url: "https://pub.dev" - source: hosted - version: "2.0.4" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3 + sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958 + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "97f7ab9a7da96d9cf19581f5de520ceb529548498bd6b5e0ccd02d68a0d15eba" + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.2" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static - sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "6db16374bc3497d21aa0eebe674d3db9fdf82082aac0f04dc7b44e4af5b08afc" + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -1157,66 +1069,66 @@ packages: dependency: transitive description: name: source_gen - sha256: "00f8b6b586f724a8c769c96f1d517511a41661c0aede644544d8d86a1ab11142" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: "522d9b05c40ec14f479ce4428337d106c0465fedab42f514582c198460a784fe" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.4" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace - sha256: "8c463326277f68a628abab20580047b419c2ff66756fd0affd451f73f9508c11" + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" source_maps: dependency: transitive description: name: source_maps - sha256: "52de2200bb098de739794c82d09c41ac27b2e42fd7e23cce7b9c74bf653c7296" + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" url: "https://pub.dev" source: hosted - version: "0.10.10" + version: "0.10.12" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: name: stream_transform - sha256: f1d172e22a5432c042b5adfa9aff621372e4292231d9d73ad00f486ad01c2395 + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" string_scanner: dependency: transitive description: @@ -1245,130 +1157,146 @@ packages: dependency: transitive description: name: test - sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.22.0" + version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.4.20" + version: "0.5.9" time: dependency: transitive description: name: time - sha256: "267028bb7b3e87bbfd66876c6389d7101e4b14eb94fe863d3e008e497ca07844" + sha256: "83427e11d9072e038364a5e4da559e85869b227cf699a541be0da74f14140124" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" timing: dependency: transitive description: name: timing - sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" - universal_html: - dependency: transitive - description: - name: universal_html - sha256: "5ff50b7c14d201421cf5230ec389a0591c4deb5c817c9d7ccca3b26fe5f31e34" - url: "https://pub.dev" - source: hosted - version: "2.0.8" + version: "1.3.2" universal_io: dependency: transitive description: name: universal_io - sha256: "79f78ddad839ee3aae3ec7c01eb4575faf0d5c860f8e5223bc9f9c17f7f03cef" + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.2.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "568176fc8ab5ac1d88ff0db8ff28659d103851670dda55e83b485664c2309299" + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 url: "https://pub.dev" source: hosted - version: "6.1.6" + version: "6.2.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "9e262cbec69233717d5198f4d0b0c4961fa027e3685ba425442c43c64f38bb9b" + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.0.19" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "6ba7dddee26c9fae27c9203c424631109d73c8fa26cfa7bc3e35e751cb87f62e" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.2.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "360fa359ab06bcb4f7c5cd3123a2a9a4d3364d4575d27c4b33468bd4497dd094" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: a9b3ea9043eabfaadfa3fb89de67a11210d85569086d22b3854484beab8b3978 + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "5669882643b96bb6d5786637cac727c6e918a790053b09245fd4513b8a07df2a" + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.2.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: e3c3b16d3104260c10eea3b0e34272aaa57921f83148b0619f74c2eced9b7ef1 + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" vector_math: dependency: transitive description: @@ -1381,66 +1309,82 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "11.10.0" watcher: dependency: transitive description: name: watcher - sha256: e42dfcc48f67618344da967b10f62de57e04bae01d9d3af4c2596f3712a88c99 + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "3a969ddcc204a3e34e863d204b29c0752716f78b6f9cc8235083208d268a4ccd" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.0" webdriver: dependency: transitive description: name: webdriver - sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" win32: dependency: transitive description: name: win32 - sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "1.1.2" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "11541eedefbcaec9de35aa82650b695297ce668662bbd6e3911a7fabdbde589f" + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" url: "https://pub.dev" source: hosted - version: "0.2.0+2" + version: "1.0.3" xml: dependency: transitive description: name: xml - sha256: ac0e3f4bf00ba2708c33fbabbbe766300e509f8c82dbd4ab6525039813f7e2fb + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -1450,5 +1394,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=2.19.0 <3.0.0" - flutter: ">=3.3.0" + dart: ">=3.2.0 <3.3.0" + flutter: ">=3.16.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 6a2f63f3c..fd758c4de 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -4,61 +4,60 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.17.0 <3.0.0' - flutter: '>=3.0.0 <3.1.0' + sdk: '>=2.17.0 <3.3.0' + flutter: '>=3.3.0' dependencies: - auto_route: ^4.2.0 + auto_route: ^7.8.4 auto_size_text: ^3.0.0 - black_hole_flutter: ^0.3.5 + black_hole_flutter: ^1.1.0 bloc: ^8.0.3 collection: ^1.16.0 cupertino_icons: ^1.0.5 dartx: ^1.1.0 - debug_overlay: ^0.1.4 - dropdown_button2: ^1.6.2 + debug_overlay: ^0.2.11 + dropdown_button2: ^2.3.9 flutter: sdk: flutter flutter_bloc: ^8.0.1 - flutter_hooks: ^0.18.5+1 + flutter_hooks: ^0.20.3 flutter_list_view: ^1.1.22 flutter_localizations: sdk: flutter - flutter_secure_storage: ^5.0.2 + flutter_secure_storage: ^9.0.0 flutter_share: ^2.0.0 flutter_sliding_up_panel: ^2.0.1 - flutter_svg: ^1.1.1 - flutter_web_auth: ^0.4.1 + flutter_svg: ^2.0.9 + flutter_web_auth: ^0.5.0 freezed_annotation: ^2.0.3 - google_fonts: ^3.0.1 + google_fonts: ^6.1.0 hive: ^2.2.2 hive_flutter: ^1.1.0 - http: ^0.13.4 - intl: ^0.17.0 + http: ^1.1.2 + intl: ^0.18.1 json_annotation: ^4.6.0 overlay_dialog: ^0.2.2 path_provider: ^2.0.11 pdf: ^3.8.1 - popover: ^0.2.8+1 printing: ^5.9.3 provider: ^6.1.1 shared_preferences: ^2.0.15 url_launcher: ^6.1.4 dev_dependencies: - auto_route_generator: ^4.2.0 + auto_route_generator: ^7.3.2 bloc_test: ^9.0.3 build_runner: ^2.1.11 - flutter_launcher_icons: ^0.9.3 - flutter_lints: ^2.0.1 + flutter_launcher_icons: ^0.13.1 + flutter_lints: ^3.0.1 flutter_test: sdk: flutter freezed: ^2.0.3+1 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 integration_test: sdk: flutter json_serializable: ^6.2.0 - mocktail: ^0.3.0 + mocktail: ^1.0.2 flutter: generate: true