From 8a259a7d11c711c3ddd9dbc42a08be203f765c4a Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Mon, 1 Sep 2025 10:54:43 -0300 Subject: [PATCH 1/6] chore: add GitHub workflows, templates and configuration files --- todo/.github/PULL_REQUEST_TEMPLATE.md | 31 + todo/.github/cspell.json | 32 + todo/.github/dependabot.yaml | 18 + todo/.github/workflows/api_client.yaml | 20 + todo/.github/workflows/auth_client.yaml | 20 + todo/.github/workflows/form_inputs.yaml | 20 + todo/.github/workflows/main.yaml | 31 + todo/.github/workflows/todo_ui.yaml | 23 + todo/.github/workflows/token_provider.yaml | 20 + todo/.github/workflows/user_repository.yaml | 20 + todo/.gitignore | 117 ++ todo/.idea/runConfigurations/development.xml | 7 + todo/.idea/runConfigurations/production.xml | 7 + todo/.idea/runConfigurations/staging.xml | 7 + todo/.metadata | 33 + todo/.vscode/extensions.json | 9 + todo/.vscode/launch.json | 95 ++ todo/README.md | 184 +++ todo/analysis_options.yaml | 8 + todo/android/.gitignore | 11 + todo/android/app/build.gradle | 115 ++ .../android/app/src/debug/AndroidManifest.xml | 8 + .../res/drawable-v21/launch_background.xml | 12 + .../res/drawable/launch_background.xml | 12 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes todo/android/app/src/main/AndroidManifest.xml | 34 + .../main/kotlin/com/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 8 + .../res/drawable-v21/launch_background.xml | 12 + .../res/drawable/launch_background.xml | 12 + .../staging/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../staging/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../staging/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../staging/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes todo/android/build.gradle | 18 + todo/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + todo/android/gradlew | 160 +++ todo/android/gradlew.bat | 90 ++ todo/android/settings.gradle | 26 + todo/app-keys.copy.json | 4 + todo/coverage_badge.svg | 20 + todo/ios/.gitignore | 34 + todo/ios/Flutter/AppFrameworkInfo.plist | 26 + todo/ios/Flutter/Debug.xcconfig | 1 + todo/ios/Flutter/Release.xcconfig | 1 + todo/ios/Runner.xcodeproj/project.pbxproj | 1182 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 ++ .../xcschemes/development.xcscheme | 78 ++ .../xcschemes/production.xcscheme | 78 ++ .../xcshareddata/xcschemes/staging.xcscheme | 78 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + todo/ios/Runner/AppDelegate.swift | 13 + .../AppIcon-dev.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../AppIcon-stg.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 41 + todo/ios/Runner/Base.lproj/Main.storyboard | 26 + todo/ios/Runner/Info.plist | 56 + todo/ios/Runner/Runner-Bridging-Header.h | 1 + todo/ios/RunnerTests/RunnerTests.swift | 12 + todo/l10n.yaml | 4 + todo/lib/app/app.dart | 4 + todo/lib/app/bloc/app_bloc.dart | 138 ++ todo/lib/app/bloc/app_event.dart | 44 + todo/lib/app/bloc/app_state.dart | 74 ++ todo/lib/app/constants/constants.dart | 1 + todo/lib/app/constants/navigation_keys.dart | 3 + todo/lib/app/extensions/build_context.dart | 8 + todo/lib/app/extensions/extensions.dart | 1 + todo/lib/app/router/app_router.dart | 101 ++ todo/lib/app/router/auth_stream_scope.dart | 39 + .../lib/app/router/navigation_bar_router.dart | 34 + todo/lib/app/router/router.dart | 3 + todo/lib/app/view/app.dart | 69 + .../bloc/delete_account_bloc.dart | 84 ++ .../bloc/delete_account_event.dart | 44 + .../bloc/delete_account_state.dart | 51 + .../delete_account/delete_account.dart | 2 + .../view/delete_account_page.dart | 26 + .../view/delete_account_view.dart | 112 ++ .../delete_account/view/view.dart | 2 + .../bloc/forgot_password_bloc.dart | 40 + .../bloc/forgot_password_event.dart | 21 + .../bloc/forgot_password_state.dart | 49 + .../forgot_password/forgot_password.dart | 2 + .../view/forgot_password_page.dart | 23 + .../view/forgot_password_view.dart | 106 ++ .../forgot_password/view/view.dart | 2 + .../authentication/login/bloc/login_bloc.dart | 62 + .../login/bloc/login_event.dart | 40 + .../login/bloc/login_state.dart | 43 + todo/lib/authentication/login/login.dart | 2 + .../authentication/login/view/login_page.dart | 23 + .../authentication/login/view/login_view.dart | 151 +++ todo/lib/authentication/login/view/view.dart | 2 + .../sign_up/bloc/sign_up_bloc.dart | 79 ++ .../sign_up/bloc/sign_up_event.dart | 58 + .../sign_up/bloc/sign_up_state.dart | 78 ++ todo/lib/authentication/sign_up/sign_up.dart | 2 + .../sign_up/view/sign_up_page.dart | 22 + .../sign_up/view/sign_up_view.dart | 192 +++ .../lib/authentication/sign_up/view/view.dart | 2 + .../down_for_maintenance.dart | 2 + .../down_for_maintenance_page.dart | 16 + .../down_for_maintenance_view.dart | 38 + todo/lib/force_upgrade/force_upgrade.dart | 2 + .../lib/force_upgrade/force_upgrade_page.dart | 16 + .../lib/force_upgrade/force_upgrade_view.dart | 39 + todo/lib/home/bloc/home_bloc.dart | 13 + todo/lib/home/bloc/home_event.dart | 17 + todo/lib/home/bloc/home_state.dart | 29 + todo/lib/home/home.dart | 2 + todo/lib/home/view/home_page.dart | 22 + todo/lib/home/view/home_view.dart | 68 + .../home/view/main_screen_page_builder.dart | 49 + todo/lib/home/view/view.dart | 3 + todo/lib/l10n/arb/app_en.arb | 131 ++ todo/lib/l10n/arb/app_localizations.dart | 320 +++++ todo/lib/l10n/arb/app_localizations_en.dart | 111 ++ todo/lib/l10n/l10n.dart | 8 + todo/lib/main/bootstrap.dart | 33 + todo/lib/main/main.dart | 1 + todo/lib/main/main_common.dart | 35 + todo/lib/main/main_development.dart | 14 + todo/lib/main/main_production.dart | 14 + todo/lib/main/main_staging.dart | 14 + todo/lib/navigation/navigation.dart | 1 + todo/lib/navigation/view/bottom_nav_bar.dart | 34 + todo/lib/navigation/view/view.dart | 1 + todo/lib/settings/bloc/settings_bloc.dart | 68 + todo/lib/settings/bloc/settings_event.dart | 20 + todo/lib/settings/bloc/settings_state.dart | 40 + todo/lib/settings/settings.dart | 2 + todo/lib/settings/view/settings_page.dart | 25 + todo/lib/settings/view/settings_view.dart | 113 ++ todo/lib/settings/view/view.dart | 2 + todo/packages/api_client/.gitignore | 7 + .../packages/api_client/analysis_options.yaml | 4 + todo/packages/api_client/lib/api_client.dart | 7 + .../api_client/lib/src/api_client.dart | 27 + .../api_client/lib/src/http_api_client.dart | 232 ++++ .../src/resources/app_config/app_config.dart | 3 + .../app_config/app_config_resource.dart | 62 + .../app_config/exceptions/exceptions.dart | 31 + .../resources/app_config/models/models.dart | 1 + .../resources/app_config/models/upgrade.dart | 46 + .../app_config/models/upgrade.g.dart | 21 + .../lib/src/resources/resources.dart | 2 + .../resources/user/exceptions/exceptions.dart | 71 + .../lib/src/resources/user/models/models.dart | 4 + .../user/models/sign_up_request.dart | 39 + .../user/models/sign_up_request.g.dart | 21 + .../user/models/update_password_request.dart | 39 + .../models/update_password_request.g.dart | 23 + .../src/resources/user/models/user_data.dart | 43 + .../resources/user/models/user_data.g.dart | 19 + .../user/models/verification_request.dart | 35 + .../user/models/verification_request.g.dart | 21 + .../lib/src/resources/user/user.dart | 3 + .../lib/src/resources/user/user_resource.dart | 173 +++ .../api_client/lib/src/utils/utils.dart | 8 + todo/packages/api_client/pubspec.yaml | 23 + .../api_client/test/src/api_client_test.dart | 31 + .../test/src/http_api_client_test.dart | 498 +++++++ .../app_config/app_config_resources_test.dart | 73 + .../exceptions/exceptions_test.dart | 43 + .../app_config/models/upgrade_test.dart | 44 + .../user/exceptions/exceptions_test.dart | 52 + .../user/models/sign_up_requested_test.dart | 54 + .../update_password_requested_test.dart | 54 + .../resources/user/models/user_data_test.dart | 54 + .../models/verification_request_test.dart | 37 + .../resources/user/user_resources_test.dart | 458 +++++++ .../api_client/test/src/utils/utils_test.dart | 22 + .../analysis_options.yaml | 4 + .../packages/app_config_repository/build.yaml | 12 + .../lib/app_config_repository.dart | 5 + .../lib/src/app_config_repository.dart | 56 + .../lib/src/models/app_config.dart | 49 + .../lib/src/models/app_config.g.dart | 33 + .../lib/src/models/force_upgrade.dart | 41 + .../lib/src/models/models.dart | 2 + .../app_config_repository/pubspec.yaml | 21 + .../test/app_config_repository_test.dart | 87 ++ .../test/models/app_config_test.dart | 86 ++ .../test/models/force_upgrade_test.dart | 53 + todo/packages/auth_client/.gitignore | 4 + .../auth_client/analysis_options.yaml | 4 + .../packages/auth_client/lib/auth_client.dart | 6 + .../auth_client/lib/src/auth_client.dart | 97 ++ .../lib/src/exceptions/exceptions.dart | 63 + .../lib/src/models/authentication_event.dart | 52 + .../lib/src/models/authentication_user.dart | 30 + .../auth_client/lib/src/models/models.dart | 2 + todo/packages/auth_client/pubspec.yaml | 18 + .../test/src/auth_client_test.dart | 74 ++ .../test/src/exceptions/exceptions_test.dart | 52 + .../src/models/authentication_event_test.dart | 82 ++ .../src/models/authentication_user_test.dart | 54 + todo/packages/form_inputs/.gitignore | 39 + todo/packages/form_inputs/LICENSE | 19 + todo/packages/form_inputs/README.md | 3 + .../form_inputs/analysis_options.yaml | 4 + .../packages/form_inputs/lib/form_inputs.dart | 11 + .../form_inputs/lib/src/address_label.dart | 34 + todo/packages/form_inputs/lib/src/email.dart | 45 + .../form_inputs/lib/src/form_status_enum.dart | 20 + todo/packages/form_inputs/lib/src/name.dart | 40 + .../form_inputs/lib/src/otp_code.dart | 31 + .../form_inputs/lib/src/password.dart | 69 + .../form_inputs/lib/src/phone_number.dart | 40 + todo/packages/form_inputs/lib/src/src.dart | 8 + .../form_inputs/lib/src/username.dart | 42 + .../packages/form_inputs/lib/src/zipcode.dart | 48 + todo/packages/form_inputs/pubspec.yaml | 14 + .../form_inputs/test/address_label_test.dart | 44 + .../packages/form_inputs/test/email_test.dart | 60 + todo/packages/form_inputs/test/name_test.dart | 44 + .../form_inputs/test/otp_code_test.dart | 44 + .../form_inputs/test/password_test.dart | 45 + .../form_inputs/test/phone_number_test.dart | 44 + .../form_inputs/test/username_test.dart | 44 + .../form_inputs/test/zipcode_test.dart | 45 + todo/packages/todo_ui/.gitignore | 5 + todo/packages/todo_ui/README.md | 121 ++ todo/packages/todo_ui/analysis_options.yaml | 4 + .../assets/fonts/NotoSansDisplay-Regular.ttf | Bin 0 -> 113460 bytes .../assets/fonts/NotoSansDisplay-SemiBold.ttf | Bin 0 -> 113440 bytes .../todo_ui/assets/icons/back_icon.svg | 3 + .../todo_ui/assets/icons/email_outline.svg | 3 + todo/packages/todo_ui/assets/images/icon.png | Bin 0 -> 544 bytes todo/packages/todo_ui/gallery/.gitignore | 41 + todo/packages/todo_ui/gallery/.metadata | 10 + todo/packages/todo_ui/gallery/README.md | 9 + .../todo_ui/gallery/analysis_options.yaml | 5 + .../todo_ui/gallery/android/.gitignore | 13 + .../todo_ui/gallery/android/app/build.gradle | 68 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 34 + .../kotlin/com/ui/gallery/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../todo_ui/gallery/android/build.gradle | 31 + .../todo_ui/gallery/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../todo_ui/gallery/android/settings.gradle | 11 + todo/packages/todo_ui/gallery/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../gallery/ios/Flutter/Debug.xcconfig | 2 + .../gallery/ios/Flutter/Release.xcconfig | 2 + todo/packages/todo_ui/gallery/ios/Podfile | 41 + .../ios/Runner.xcodeproj/project.pbxproj | 481 +++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../gallery/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../todo_ui/gallery/ios/Runner/Info.plist | 45 + .../ios/Runner/Runner-Bridging-Header.h | 1 + .../todo_ui/gallery/lib/colors/colors.dart | 1 + .../gallery/lib/colors/colors_page.dart | 179 +++ todo/packages/todo_ui/gallery/lib/main.dart | 129 ++ .../todo_ui/gallery/lib/spacing/spacing.dart | 1 + .../gallery/lib/spacing/spacing_page.dart | 73 + .../gallery/lib/typography/typography.dart | 1 + .../lib/typography/typography_page.dart | 62 + .../gallery/lib/widgets/app_bar_page.dart | 45 + .../gallery/lib/widgets/button_page.dart | 120 ++ .../gallery/lib/widgets/icons_page.dart | 46 + .../gallery/lib/widgets/text_field_page.dart | 40 + .../todo_ui/gallery/lib/widgets/widgets.dart | 5 + .../gallery/lib/widgets/widgets_page.dart | 80 ++ todo/packages/todo_ui/gallery/pubspec.yaml | 20 + todo/packages/todo_ui/gallery/web/favicon.png | Bin 0 -> 917 bytes .../todo_ui/gallery/web/icons/Icon-192.png | Bin 0 -> 840 bytes .../todo_ui/gallery/web/icons/Icon-512.png | Bin 0 -> 2172 bytes .../gallery/web/icons/Icon-maskable-192.png | Bin 0 -> 1280 bytes .../gallery/web/icons/Icon-maskable-512.png | Bin 0 -> 3871 bytes todo/packages/todo_ui/gallery/web/index.html | 104 ++ .../todo_ui/gallery/web/manifest.json | 35 + .../todo_ui/lib/src/colors/todo_colors.dart | 93 ++ .../lib/src/extensions/extensions.dart | 1 + .../lib/src/extensions/theme_data.dart | 15 + .../packages/todo_ui/lib/src/icons/icons.dart | 3 + .../todo_ui/lib/src/icons/todo_icons.dart | 26 + .../lib/src/icons/todo_icons_dark.dart | 22 + .../lib/src/icons/todo_icons_light.dart | 22 + .../todo_ui/lib/src/spacing/todo_spacing.dart | 38 + .../todo_ui/lib/src/theme/todo_theme.dart | 426 ++++++ .../lib/src/typography/todo_font_weight.dart | 31 + .../lib/src/typography/todo_text_styles.dart | 106 ++ .../lib/src/typography/typography.dart | 2 + .../todo_ui/lib/src/widgets/todo_app_bar.dart | 120 ++ .../lib/src/widgets/todo_outlined_button.dart | 110 ++ .../lib/src/widgets/todo_text_field.dart | 169 +++ .../todo_ui/lib/src/widgets/widgets.dart | 3 + todo/packages/todo_ui/lib/todo_ui.dart | 7 + todo/packages/todo_ui/pubspec.yaml | 44 + .../test/src/extensions/theme_data_test.dart | 17 + .../todo_ui/test/src/helpers/helpers.dart | 1 + .../todo_ui/test/src/helpers/pump_app.dart | 28 + .../test/src/icons/todo_icons_dark_test.dart | 21 + .../test/src/icons/todo_icons_light_test.dart | 21 + .../test/src/icons/todo_icons_test.dart | 23 + .../src/typography/todo_text_styles_test.dart | 61 + .../test/src/widgets/todo_app_bar_test.dart | 44 + .../widgets/todo_outlined_button_test.dart | 125 ++ .../src/widgets/todo_text_field_test.dart | 58 + .../todo_ui/test/theme/todo_theme_test.dart | 324 +++++ todo/packages/token_provider/.gitignore | 7 + .../token_provider/analysis_options.yaml | 4 + .../lib/src/token_provider.dart | 14 + .../token_provider/lib/token_provider.dart | 4 + todo/packages/token_provider/pubspec.yaml | 12 + .../test/src/token_provider_test.dart | 22 + todo/packages/user_repository/.gitignore | 7 + .../user_repository/analysis_options.yaml | 4 + .../lib/src/exceptions/exceptions.dart | 39 + .../lib/src/models/models.dart | 1 + .../user_repository/lib/src/models/user.dart | 38 + .../lib/src/models/user.g.dart | 17 + .../lib/src/user_repository.dart | 243 ++++ .../user_repository/lib/user_repository.dart | 6 + todo/packages/user_repository/pubspec.yaml | 22 + .../test/src/exceptions/exceptions_test.dart | 36 + .../test/src/models/user_test.dart | 48 + .../test/src/user_repository_test.dart | 546 ++++++++ todo/pubspec.yaml | 44 + todo/test/app/bloc/app_bloc_test.dart | 213 +++ todo/test/app/bloc/app_event_test.dart | 53 + todo/test/app/bloc/app_state_test.dart | 37 + .../app/extensions/build_context_test.dart | 44 + todo/test/app/view/app_view_test.dart | 72 + .../bloc/delete_account_bloc_test.dart | 163 +++ .../bloc/delete_account_event_test.dart | 60 + .../view/delete_account_page_test.dart | 38 + .../view/delete_account_view_test.dart | 86 ++ .../bloc/forgot_password_bloc_test.dart | 76 ++ .../bloc/forgot_password_event_test.dart | 36 + .../bloc/forgot_password_state_test.dart | 120 ++ .../view/forgot_password_view_test.dart | 137 ++ .../login/bloc/login_bloc_test.dart | 106 ++ .../login/bloc/login_event_test.dart | 70 + .../login/view/forgot_password_page_test.dart | 38 + .../login/view/login_view_test.dart | 255 ++++ .../sign_up/bloc/sign_up_bloc_test.dart | 141 ++ .../sign_up/bloc/sign_up_event_test.dart | 108 ++ .../sign_up/bloc/sign_up_state_test.dart | 85 ++ .../sign_up/view/sign_up_page_test.dart | 38 + .../sign_up/view/sign_up_view_test.dart | 399 ++++++ .../down_for_maintenance_page_test.dart | 39 + .../down_for_maintenance_view_test.dart | 44 + .../force_upgrade_page_test.dart | 38 + .../force_upgrade_view_test.dart | 69 + todo/test/helpers/helpers.dart | 1 + todo/test/helpers/pump_app.dart | 137 ++ todo/test/home/bloc/home_bloc_test.dart | 13 + todo/test/home/bloc/home_event_test.dart | 12 + todo/test/home/bloc/home_state_test.dart | 23 + todo/test/home/view/home_page_test.dart | 38 + todo/test/home/view/home_view_test.dart | 105 ++ .../settings/bloc/settings_bloc_test.dart | 26 + .../settings/bloc/settings_event_test.dart | 22 + .../settings/bloc/settings_state_test.dart | 32 + .../settings/view/settings_view_test.dart | 107 ++ todo/web/favicon.png | Bin 0 -> 917 bytes todo/web/icons/Icon-192.png | Bin 0 -> 5292 bytes todo/web/icons/Icon-512.png | Bin 0 -> 8252 bytes todo/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes todo/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes todo/web/index.html | 50 + todo/web/manifest.json | 23 + 476 files changed, 19447 insertions(+) create mode 100644 todo/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 todo/.github/cspell.json create mode 100644 todo/.github/dependabot.yaml create mode 100644 todo/.github/workflows/api_client.yaml create mode 100644 todo/.github/workflows/auth_client.yaml create mode 100644 todo/.github/workflows/form_inputs.yaml create mode 100644 todo/.github/workflows/main.yaml create mode 100644 todo/.github/workflows/todo_ui.yaml create mode 100644 todo/.github/workflows/token_provider.yaml create mode 100644 todo/.github/workflows/user_repository.yaml create mode 100644 todo/.gitignore create mode 100644 todo/.idea/runConfigurations/development.xml create mode 100644 todo/.idea/runConfigurations/production.xml create mode 100644 todo/.idea/runConfigurations/staging.xml create mode 100644 todo/.metadata create mode 100644 todo/.vscode/extensions.json create mode 100644 todo/.vscode/launch.json create mode 100644 todo/README.md create mode 100644 todo/analysis_options.yaml create mode 100644 todo/android/.gitignore create mode 100644 todo/android/app/build.gradle create mode 100644 todo/android/app/src/debug/AndroidManifest.xml create mode 100644 todo/android/app/src/development/res/drawable-v21/launch_background.xml create mode 100644 todo/android/app/src/development/res/drawable/launch_background.xml create mode 100644 todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png create mode 100644 todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png create mode 100644 todo/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png create mode 100644 todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/AndroidManifest.xml create mode 100644 todo/android/app/src/main/kotlin/com/example/MainActivity.kt create mode 100644 todo/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 todo/android/app/src/main/res/drawable/launch_background.xml create mode 100644 todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 todo/android/app/src/main/res/values-night/styles.xml create mode 100644 todo/android/app/src/main/res/values/styles.xml create mode 100644 todo/android/app/src/profile/AndroidManifest.xml create mode 100644 todo/android/app/src/staging/res/drawable-v21/launch_background.xml create mode 100644 todo/android/app/src/staging/res/drawable/launch_background.xml create mode 100644 todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png create mode 100644 todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png create mode 100644 todo/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png create mode 100644 todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 todo/android/build.gradle create mode 100644 todo/android/gradle.properties create mode 100644 todo/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 todo/android/gradlew create mode 100644 todo/android/gradlew.bat create mode 100644 todo/android/settings.gradle create mode 100644 todo/app-keys.copy.json create mode 100644 todo/coverage_badge.svg create mode 100644 todo/ios/.gitignore create mode 100644 todo/ios/Flutter/AppFrameworkInfo.plist create mode 100644 todo/ios/Flutter/Debug.xcconfig create mode 100644 todo/ios/Flutter/Release.xcconfig create mode 100644 todo/ios/Runner.xcodeproj/project.pbxproj create mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme create mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme create mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme create mode 100644 todo/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 todo/ios/Runner/AppDelegate.swift create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 todo/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 todo/ios/Runner/Base.lproj/Main.storyboard create mode 100644 todo/ios/Runner/Info.plist create mode 100644 todo/ios/Runner/Runner-Bridging-Header.h create mode 100644 todo/ios/RunnerTests/RunnerTests.swift create mode 100644 todo/l10n.yaml create mode 100644 todo/lib/app/app.dart create mode 100644 todo/lib/app/bloc/app_bloc.dart create mode 100644 todo/lib/app/bloc/app_event.dart create mode 100644 todo/lib/app/bloc/app_state.dart create mode 100644 todo/lib/app/constants/constants.dart create mode 100644 todo/lib/app/constants/navigation_keys.dart create mode 100644 todo/lib/app/extensions/build_context.dart create mode 100644 todo/lib/app/extensions/extensions.dart create mode 100644 todo/lib/app/router/app_router.dart create mode 100644 todo/lib/app/router/auth_stream_scope.dart create mode 100644 todo/lib/app/router/navigation_bar_router.dart create mode 100644 todo/lib/app/router/router.dart create mode 100644 todo/lib/app/view/app.dart create mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart create mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_event.dart create mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_state.dart create mode 100644 todo/lib/authentication/delete_account/delete_account.dart create mode 100644 todo/lib/authentication/delete_account/view/delete_account_page.dart create mode 100644 todo/lib/authentication/delete_account/view/delete_account_view.dart create mode 100644 todo/lib/authentication/delete_account/view/view.dart create mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart create mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart create mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart create mode 100644 todo/lib/authentication/forgot_password/forgot_password.dart create mode 100644 todo/lib/authentication/forgot_password/view/forgot_password_page.dart create mode 100644 todo/lib/authentication/forgot_password/view/forgot_password_view.dart create mode 100644 todo/lib/authentication/forgot_password/view/view.dart create mode 100644 todo/lib/authentication/login/bloc/login_bloc.dart create mode 100644 todo/lib/authentication/login/bloc/login_event.dart create mode 100644 todo/lib/authentication/login/bloc/login_state.dart create mode 100644 todo/lib/authentication/login/login.dart create mode 100644 todo/lib/authentication/login/view/login_page.dart create mode 100644 todo/lib/authentication/login/view/login_view.dart create mode 100644 todo/lib/authentication/login/view/view.dart create mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart create mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_event.dart create mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_state.dart create mode 100644 todo/lib/authentication/sign_up/sign_up.dart create mode 100644 todo/lib/authentication/sign_up/view/sign_up_page.dart create mode 100644 todo/lib/authentication/sign_up/view/sign_up_view.dart create mode 100644 todo/lib/authentication/sign_up/view/view.dart create mode 100644 todo/lib/down_for_maintenance/down_for_maintenance.dart create mode 100644 todo/lib/down_for_maintenance/down_for_maintenance_page.dart create mode 100644 todo/lib/down_for_maintenance/down_for_maintenance_view.dart create mode 100644 todo/lib/force_upgrade/force_upgrade.dart create mode 100644 todo/lib/force_upgrade/force_upgrade_page.dart create mode 100644 todo/lib/force_upgrade/force_upgrade_view.dart create mode 100644 todo/lib/home/bloc/home_bloc.dart create mode 100644 todo/lib/home/bloc/home_event.dart create mode 100644 todo/lib/home/bloc/home_state.dart create mode 100644 todo/lib/home/home.dart create mode 100644 todo/lib/home/view/home_page.dart create mode 100644 todo/lib/home/view/home_view.dart create mode 100644 todo/lib/home/view/main_screen_page_builder.dart create mode 100644 todo/lib/home/view/view.dart create mode 100644 todo/lib/l10n/arb/app_en.arb create mode 100644 todo/lib/l10n/arb/app_localizations.dart create mode 100644 todo/lib/l10n/arb/app_localizations_en.dart create mode 100644 todo/lib/l10n/l10n.dart create mode 100644 todo/lib/main/bootstrap.dart create mode 100644 todo/lib/main/main.dart create mode 100644 todo/lib/main/main_common.dart create mode 100644 todo/lib/main/main_development.dart create mode 100644 todo/lib/main/main_production.dart create mode 100644 todo/lib/main/main_staging.dart create mode 100644 todo/lib/navigation/navigation.dart create mode 100644 todo/lib/navigation/view/bottom_nav_bar.dart create mode 100644 todo/lib/navigation/view/view.dart create mode 100644 todo/lib/settings/bloc/settings_bloc.dart create mode 100644 todo/lib/settings/bloc/settings_event.dart create mode 100644 todo/lib/settings/bloc/settings_state.dart create mode 100644 todo/lib/settings/settings.dart create mode 100644 todo/lib/settings/view/settings_page.dart create mode 100644 todo/lib/settings/view/settings_view.dart create mode 100644 todo/lib/settings/view/view.dart create mode 100644 todo/packages/api_client/.gitignore create mode 100644 todo/packages/api_client/analysis_options.yaml create mode 100644 todo/packages/api_client/lib/api_client.dart create mode 100644 todo/packages/api_client/lib/src/api_client.dart create mode 100644 todo/packages/api_client/lib/src/http_api_client.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/app_config.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/models.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart create mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart create mode 100644 todo/packages/api_client/lib/src/resources/resources.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/models.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/user_data.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/verification_request.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/user.dart create mode 100644 todo/packages/api_client/lib/src/resources/user/user_resource.dart create mode 100644 todo/packages/api_client/lib/src/utils/utils.dart create mode 100644 todo/packages/api_client/pubspec.yaml create mode 100644 todo/packages/api_client/test/src/api_client_test.dart create mode 100644 todo/packages/api_client/test/src/http_api_client_test.dart create mode 100644 todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart create mode 100644 todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart create mode 100644 todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/models/user_data_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart create mode 100644 todo/packages/api_client/test/src/resources/user/user_resources_test.dart create mode 100644 todo/packages/api_client/test/src/utils/utils_test.dart create mode 100644 todo/packages/app_config_repository/analysis_options.yaml create mode 100644 todo/packages/app_config_repository/build.yaml create mode 100644 todo/packages/app_config_repository/lib/app_config_repository.dart create mode 100644 todo/packages/app_config_repository/lib/src/app_config_repository.dart create mode 100644 todo/packages/app_config_repository/lib/src/models/app_config.dart create mode 100644 todo/packages/app_config_repository/lib/src/models/app_config.g.dart create mode 100644 todo/packages/app_config_repository/lib/src/models/force_upgrade.dart create mode 100644 todo/packages/app_config_repository/lib/src/models/models.dart create mode 100644 todo/packages/app_config_repository/pubspec.yaml create mode 100644 todo/packages/app_config_repository/test/app_config_repository_test.dart create mode 100644 todo/packages/app_config_repository/test/models/app_config_test.dart create mode 100644 todo/packages/app_config_repository/test/models/force_upgrade_test.dart create mode 100644 todo/packages/auth_client/.gitignore create mode 100644 todo/packages/auth_client/analysis_options.yaml create mode 100644 todo/packages/auth_client/lib/auth_client.dart create mode 100644 todo/packages/auth_client/lib/src/auth_client.dart create mode 100644 todo/packages/auth_client/lib/src/exceptions/exceptions.dart create mode 100644 todo/packages/auth_client/lib/src/models/authentication_event.dart create mode 100644 todo/packages/auth_client/lib/src/models/authentication_user.dart create mode 100644 todo/packages/auth_client/lib/src/models/models.dart create mode 100644 todo/packages/auth_client/pubspec.yaml create mode 100644 todo/packages/auth_client/test/src/auth_client_test.dart create mode 100644 todo/packages/auth_client/test/src/exceptions/exceptions_test.dart create mode 100644 todo/packages/auth_client/test/src/models/authentication_event_test.dart create mode 100644 todo/packages/auth_client/test/src/models/authentication_user_test.dart create mode 100644 todo/packages/form_inputs/.gitignore create mode 100644 todo/packages/form_inputs/LICENSE create mode 100644 todo/packages/form_inputs/README.md create mode 100644 todo/packages/form_inputs/analysis_options.yaml create mode 100644 todo/packages/form_inputs/lib/form_inputs.dart create mode 100644 todo/packages/form_inputs/lib/src/address_label.dart create mode 100644 todo/packages/form_inputs/lib/src/email.dart create mode 100644 todo/packages/form_inputs/lib/src/form_status_enum.dart create mode 100644 todo/packages/form_inputs/lib/src/name.dart create mode 100644 todo/packages/form_inputs/lib/src/otp_code.dart create mode 100644 todo/packages/form_inputs/lib/src/password.dart create mode 100644 todo/packages/form_inputs/lib/src/phone_number.dart create mode 100644 todo/packages/form_inputs/lib/src/src.dart create mode 100644 todo/packages/form_inputs/lib/src/username.dart create mode 100644 todo/packages/form_inputs/lib/src/zipcode.dart create mode 100644 todo/packages/form_inputs/pubspec.yaml create mode 100644 todo/packages/form_inputs/test/address_label_test.dart create mode 100644 todo/packages/form_inputs/test/email_test.dart create mode 100644 todo/packages/form_inputs/test/name_test.dart create mode 100644 todo/packages/form_inputs/test/otp_code_test.dart create mode 100644 todo/packages/form_inputs/test/password_test.dart create mode 100644 todo/packages/form_inputs/test/phone_number_test.dart create mode 100644 todo/packages/form_inputs/test/username_test.dart create mode 100644 todo/packages/form_inputs/test/zipcode_test.dart create mode 100644 todo/packages/todo_ui/.gitignore create mode 100644 todo/packages/todo_ui/README.md create mode 100644 todo/packages/todo_ui/analysis_options.yaml create mode 100644 todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf create mode 100644 todo/packages/todo_ui/assets/fonts/NotoSansDisplay-SemiBold.ttf create mode 100644 todo/packages/todo_ui/assets/icons/back_icon.svg create mode 100644 todo/packages/todo_ui/assets/icons/email_outline.svg create mode 100644 todo/packages/todo_ui/assets/images/icon.png create mode 100644 todo/packages/todo_ui/gallery/.gitignore create mode 100644 todo/packages/todo_ui/gallery/.metadata create mode 100644 todo/packages/todo_ui/gallery/README.md create mode 100644 todo/packages/todo_ui/gallery/analysis_options.yaml create mode 100644 todo/packages/todo_ui/gallery/android/.gitignore create mode 100644 todo/packages/todo_ui/gallery/android/app/build.gradle create mode 100644 todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml create mode 100644 todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml create mode 100644 todo/packages/todo_ui/gallery/android/build.gradle create mode 100644 todo/packages/todo_ui/gallery/android/gradle.properties create mode 100644 todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 todo/packages/todo_ui/gallery/android/settings.gradle create mode 100644 todo/packages/todo_ui/gallery/ios/.gitignore create mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist create mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig create mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig create mode 100644 todo/packages/todo_ui/gallery/ios/Podfile create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Info.plist create mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h create mode 100644 todo/packages/todo_ui/gallery/lib/colors/colors.dart create mode 100644 todo/packages/todo_ui/gallery/lib/colors/colors_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/main.dart create mode 100644 todo/packages/todo_ui/gallery/lib/spacing/spacing.dart create mode 100644 todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/typography/typography.dart create mode 100644 todo/packages/todo_ui/gallery/lib/typography/typography_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/button_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/widgets.dart create mode 100644 todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart create mode 100644 todo/packages/todo_ui/gallery/pubspec.yaml create mode 100644 todo/packages/todo_ui/gallery/web/favicon.png create mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-192.png create mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-512.png create mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-maskable-192.png create mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-maskable-512.png create mode 100644 todo/packages/todo_ui/gallery/web/index.html create mode 100644 todo/packages/todo_ui/gallery/web/manifest.json create mode 100644 todo/packages/todo_ui/lib/src/colors/todo_colors.dart create mode 100644 todo/packages/todo_ui/lib/src/extensions/extensions.dart create mode 100644 todo/packages/todo_ui/lib/src/extensions/theme_data.dart create mode 100644 todo/packages/todo_ui/lib/src/icons/icons.dart create mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons.dart create mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart create mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart create mode 100644 todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart create mode 100644 todo/packages/todo_ui/lib/src/theme/todo_theme.dart create mode 100644 todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart create mode 100644 todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart create mode 100644 todo/packages/todo_ui/lib/src/typography/typography.dart create mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart create mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart create mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart create mode 100644 todo/packages/todo_ui/lib/src/widgets/widgets.dart create mode 100644 todo/packages/todo_ui/lib/todo_ui.dart create mode 100644 todo/packages/todo_ui/pubspec.yaml create mode 100644 todo/packages/todo_ui/test/src/extensions/theme_data_test.dart create mode 100644 todo/packages/todo_ui/test/src/helpers/helpers.dart create mode 100644 todo/packages/todo_ui/test/src/helpers/pump_app.dart create mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart create mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart create mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_test.dart create mode 100644 todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart create mode 100644 todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart create mode 100644 todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart create mode 100644 todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart create mode 100644 todo/packages/todo_ui/test/theme/todo_theme_test.dart create mode 100644 todo/packages/token_provider/.gitignore create mode 100644 todo/packages/token_provider/analysis_options.yaml create mode 100644 todo/packages/token_provider/lib/src/token_provider.dart create mode 100644 todo/packages/token_provider/lib/token_provider.dart create mode 100644 todo/packages/token_provider/pubspec.yaml create mode 100644 todo/packages/token_provider/test/src/token_provider_test.dart create mode 100644 todo/packages/user_repository/.gitignore create mode 100644 todo/packages/user_repository/analysis_options.yaml create mode 100644 todo/packages/user_repository/lib/src/exceptions/exceptions.dart create mode 100644 todo/packages/user_repository/lib/src/models/models.dart create mode 100644 todo/packages/user_repository/lib/src/models/user.dart create mode 100644 todo/packages/user_repository/lib/src/models/user.g.dart create mode 100644 todo/packages/user_repository/lib/src/user_repository.dart create mode 100644 todo/packages/user_repository/lib/user_repository.dart create mode 100644 todo/packages/user_repository/pubspec.yaml create mode 100644 todo/packages/user_repository/test/src/exceptions/exceptions_test.dart create mode 100644 todo/packages/user_repository/test/src/models/user_test.dart create mode 100644 todo/packages/user_repository/test/src/user_repository_test.dart create mode 100644 todo/pubspec.yaml create mode 100644 todo/test/app/bloc/app_bloc_test.dart create mode 100644 todo/test/app/bloc/app_event_test.dart create mode 100644 todo/test/app/bloc/app_state_test.dart create mode 100644 todo/test/app/extensions/build_context_test.dart create mode 100644 todo/test/app/view/app_view_test.dart create mode 100644 todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart create mode 100644 todo/test/authentication/delete_account/bloc/delete_account_event_test.dart create mode 100644 todo/test/authentication/delete_account/view/delete_account_page_test.dart create mode 100644 todo/test/authentication/delete_account/view/delete_account_view_test.dart create mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart create mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart create mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart create mode 100644 todo/test/authentication/forgot_password/view/forgot_password_view_test.dart create mode 100644 todo/test/authentication/login/bloc/login_bloc_test.dart create mode 100644 todo/test/authentication/login/bloc/login_event_test.dart create mode 100644 todo/test/authentication/login/view/forgot_password_page_test.dart create mode 100644 todo/test/authentication/login/view/login_view_test.dart create mode 100644 todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart create mode 100644 todo/test/authentication/sign_up/bloc/sign_up_event_test.dart create mode 100644 todo/test/authentication/sign_up/bloc/sign_up_state_test.dart create mode 100644 todo/test/authentication/sign_up/view/sign_up_page_test.dart create mode 100644 todo/test/authentication/sign_up/view/sign_up_view_test.dart create mode 100644 todo/test/down_for_maintenance/down_for_maintenance_page_test.dart create mode 100644 todo/test/down_for_maintenance/down_for_maintenance_view_test.dart create mode 100644 todo/test/force_upgrade/force_upgrade_page_test.dart create mode 100644 todo/test/force_upgrade/force_upgrade_view_test.dart create mode 100644 todo/test/helpers/helpers.dart create mode 100644 todo/test/helpers/pump_app.dart create mode 100644 todo/test/home/bloc/home_bloc_test.dart create mode 100644 todo/test/home/bloc/home_event_test.dart create mode 100644 todo/test/home/bloc/home_state_test.dart create mode 100644 todo/test/home/view/home_page_test.dart create mode 100644 todo/test/home/view/home_view_test.dart create mode 100644 todo/test/settings/bloc/settings_bloc_test.dart create mode 100644 todo/test/settings/bloc/settings_event_test.dart create mode 100644 todo/test/settings/bloc/settings_state_test.dart create mode 100644 todo/test/settings/view/settings_view_test.dart create mode 100644 todo/web/favicon.png create mode 100644 todo/web/icons/Icon-192.png create mode 100644 todo/web/icons/Icon-512.png create mode 100644 todo/web/icons/Icon-maskable-192.png create mode 100644 todo/web/icons/Icon-maskable-512.png create mode 100644 todo/web/index.html create mode 100644 todo/web/manifest.json diff --git a/todo/.github/PULL_REQUEST_TEMPLATE.md b/todo/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4e11e3e --- /dev/null +++ b/todo/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +## Description + + + +## Type of Change + + + +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) +- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 🧹 Code refactor +- [ ] ✅ Build configuration change +- [ ] 📝 Documentation +- [ ] 🗑️ Chore + +## Quality agreement + + + +- [ ] 👀 Did you run the command: `flutter analyze packages lib test`? +- [ ] 👀 Did you run the command: `dart format --line-length 80 .`? +- [ ] 👀 No environment variables commited into source control (use `.env`) diff --git a/todo/.github/cspell.json b/todo/.github/cspell.json new file mode 100644 index 0000000..3286743 --- /dev/null +++ b/todo/.github/cspell.json @@ -0,0 +1,32 @@ +{ + "version": "0.2", + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "dictionaries": [ + "vgv_allowed", + "vgv_forbidden" + ], + "dictionaryDefinitions": [ + { + "name": "vgv_allowed", + "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt", + "description": "Allowed VGV Spellings" + }, + { + "name": "vgv_forbidden", + "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt", + "description": "Forbidden VGV Spellings" + } + ], + "useGitignore": true, + "words": [ + "Contador", + "localizable", + "mostrado", + "página", + "Texto", + "appbar", + "gifs", + "Noto", + "Everytime" + ] +} \ No newline at end of file diff --git a/todo/.github/dependabot.yaml b/todo/.github/dependabot.yaml new file mode 100644 index 0000000..ca17430 --- /dev/null +++ b/todo/.github/dependabot.yaml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + commit-message: + prefix: "chore" + include: "scope" + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + commit-message: + prefix: "chore" + include: "scope" diff --git a/todo/.github/workflows/api_client.yaml b/todo/.github/workflows/api_client.yaml new file mode 100644 index 0000000..1c95e1c --- /dev/null +++ b/todo/.github/workflows/api_client.yaml @@ -0,0 +1,20 @@ +name: api_client + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/api_client/**" + - ".github/workflows/api_client.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 + with: + dart_sdk: 3.4.4 + working_directory: packages/api_client + coverage_excludes: "**/*.g.dart" + min_coverage: 95 \ No newline at end of file diff --git a/todo/.github/workflows/auth_client.yaml b/todo/.github/workflows/auth_client.yaml new file mode 100644 index 0000000..62ec70b --- /dev/null +++ b/todo/.github/workflows/auth_client.yaml @@ -0,0 +1,20 @@ +name: auth_client + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/auth_client/**" + - ".github/workflows/auth_client.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + dart_sdk: 3.4.4 + working_directory: packages/auth_client + coverage_excludes: "**/*.g.dart" + min_coverage: 80 \ No newline at end of file diff --git a/todo/.github/workflows/form_inputs.yaml b/todo/.github/workflows/form_inputs.yaml new file mode 100644 index 0000000..cc01664 --- /dev/null +++ b/todo/.github/workflows/form_inputs.yaml @@ -0,0 +1,20 @@ +name: form_inputs + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/form_inputs/**" + - ".github/workflows/form_inputs.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 + with: + dart_sdk: 3.4.4 + working_directory: packages/form_inputs + coverage_excludes: "**/*.g.dart" + min_coverage: 95 \ No newline at end of file diff --git a/todo/.github/workflows/main.yaml b/todo/.github/workflows/main.yaml new file mode 100644 index 0000000..e76adcd --- /dev/null +++ b/todo/.github/workflows/main.yaml @@ -0,0 +1,31 @@ +name: todo + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + semantic-pull-request: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 + + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + flutter_channel: stable + flutter_version: 3.22.3 + min_coverage: 0 + + spell-check: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 + with: + includes: | + **/*.md + modified_files_only: false diff --git a/todo/.github/workflows/todo_ui.yaml b/todo/.github/workflows/todo_ui.yaml new file mode 100644 index 0000000..0997d51 --- /dev/null +++ b/todo/.github/workflows/todo_ui.yaml @@ -0,0 +1,23 @@ +name: todo_ui + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/todo_ui/**" + - ".github/workflows/todo_ui.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + coverage_excludes: "**/*.g.dart" + flutter_channel: stable + flutter_version: 3.22.3 + working_directory: packages/todo_ui + min_coverage: 80 + analyze_directories: lib gallery test + format_directories: lib gallery test diff --git a/todo/.github/workflows/token_provider.yaml b/todo/.github/workflows/token_provider.yaml new file mode 100644 index 0000000..097f76a --- /dev/null +++ b/todo/.github/workflows/token_provider.yaml @@ -0,0 +1,20 @@ +name: token_provider + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/token_provider/**" + - ".github/workflows/token_provider.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 + with: + dart_sdk: 3.4.4 + working_directory: packages/token_provider + coverage_excludes: "**/*.g.dart" + min_coverage: 100 \ No newline at end of file diff --git a/todo/.github/workflows/user_repository.yaml b/todo/.github/workflows/user_repository.yaml new file mode 100644 index 0000000..740b6a4 --- /dev/null +++ b/todo/.github/workflows/user_repository.yaml @@ -0,0 +1,20 @@ +name: user_repository + +concurrency: + group: $-$ + cancel-in-progress: true + +on: + pull_request: + paths: + - "packages/user_repository/**" + - ".github/workflows/user_repository.yaml" + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + dart_sdk: 3.4.4 + working_directory: packages/user_repository + coverage_excludes: "**/*.g.dart" + min_coverage: 85 \ No newline at end of file diff --git a/todo/.gitignore b/todo/.gitignore new file mode 100644 index 0000000..187bf26 --- /dev/null +++ b/todo/.gitignore @@ -0,0 +1,117 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/* + +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds +.fvm/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +**/android/.idea/ +**/android/app/debug +**/android/app/profile +**/android/app/release +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/.last_build_id +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Coverage +coverage/ + +# Submodules +packages/**/pubspec.lock + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to the above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/extensions.json +!.vscode/launch.json +!.idea/codeStyles/ +!.idea/dictionaries/ +!.idea/runConfigurations/ + +# App key files +app-keys.dev.json +app-keys.stg.json +app-keys.prod.json diff --git a/todo/.idea/runConfigurations/development.xml b/todo/.idea/runConfigurations/development.xml new file mode 100644 index 0000000..07b02c6 --- /dev/null +++ b/todo/.idea/runConfigurations/development.xml @@ -0,0 +1,7 @@ + + + + diff --git a/todo/.idea/runConfigurations/production.xml b/todo/.idea/runConfigurations/production.xml new file mode 100644 index 0000000..1c5c774 --- /dev/null +++ b/todo/.idea/runConfigurations/production.xml @@ -0,0 +1,7 @@ + + + + diff --git a/todo/.idea/runConfigurations/staging.xml b/todo/.idea/runConfigurations/staging.xml new file mode 100644 index 0000000..f979a68 --- /dev/null +++ b/todo/.idea/runConfigurations/staging.xml @@ -0,0 +1,7 @@ + + + + diff --git a/todo/.metadata b/todo/.metadata new file mode 100644 index 0000000..59a5a1f --- /dev/null +++ b/todo/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/todo/.vscode/extensions.json b/todo/.vscode/extensions.json new file mode 100644 index 0000000..5500bce --- /dev/null +++ b/todo/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dart-code.dart-code", + "dart-code.flutter", + "felixangelov.bloc" + ] +} diff --git a/todo/.vscode/launch.json b/todo/.vscode/launch.json new file mode 100644 index 0000000..356cf37 --- /dev/null +++ b/todo/.vscode/launch.json @@ -0,0 +1,95 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch development", + "request": "launch", + "type": "dart", + "program": "lib/main/main_development.dart", + "args": [ + "--flavor", + "development", + "--target", + "lib/main/main_development.dart", + "--dart-define-from-file", + "app-keys.dev.json" + ] + }, + { + "name": "Launch staging", + "request": "launch", + "type": "dart", + "program": "lib/main/main_staging.dart", + "args": [ + "--flavor", + "staging", + "--target", + "lib/main/main_staging.dart", + "--dart-define-from-file", + "app-keys.stg.json" + ] + }, + { + "name": "Launch production", + "request": "launch", + "type": "dart", + "program": "lib/main/main_production.dart", + "args": [ + "--flavor", + "production", + "--target", + "lib/main/main_production.dart", + "--dart-define-from-file", + "app-keys.prod.json" + ] + }, + { + "name": "Launch web development", + "request": "launch", + "type": "dart", + "program": "lib/main/main_development.dart", + "args": [ + "--target", + "lib/main/main_development.dart", + "--dart-define-from-file", + "app-keys.dev.json" + ] + }, + { + "name": "Launch web staging", + "request": "launch", + "type": "dart", + "program": "lib/main/main_staging.dart", + "args": [ + "--target", + "lib/main_staging.dart", + "--dart-define-from-file", + "app-keys.stg.json" + ] + }, + { + "name": "Launch web production", + "request": "launch", + "type": "dart", + "program": "lib/main/main_production.dart", + "args": [ + "--target", + "lib/main_production.dart", + "--dart-define-from-file", + "app-keys.prod.json" + ] + }, + { + "name": "Launch gallery", + "request": "launch", + "type": "dart", + "program": "packages/todo_ui/gallery/lib/main.dart" + } + ] +} + + + diff --git a/todo/README.md b/todo/README.md new file mode 100644 index 0000000..2a993e8 --- /dev/null +++ b/todo/README.md @@ -0,0 +1,184 @@ +# Todo + +![coverage][coverage_badge] +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] + +A Somnio App + +--- + +## Getting Started 🚀 + +This project contains 3 flavors: + +- development +- staging +- production + +To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands: + +```sh +# Development +$ flutter run --flavor development --target lib/main_development.dart --dart-define-from-file app-keys.dev.json + +# Staging +$ flutter run --flavor staging --target lib/main_staging.dart --dart-define-from-file app-keys.stg.json + +# Production +$ flutter run --flavor production --target lib/main_production.dart --dart-define-from-file app-keys.prod.json +``` + +_\*Todo works on iOS, Android, Web, and Windows._ + +--- +Use this example to create a file to store the environment variables: +```sh +# Keys for development flavor +$ cp app-keys.copy.json app-keys.dev.json +# Keys for staging flavor +$ cp app-keys.copy.json app-keys.stg.json +# Keys for production flavor +$ cp app-keys.copy.json app-keys.prod.json +``` + +--- + +## Running Tests 🧪 + +To run all unit and widget tests use the following command: + +```sh +$ flutter test --coverage --test-randomize-ordering-seed random +``` + +To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov). + +```sh +# Generate Coverage Report +$ genhtml coverage/lcov.info -o coverage/ + +# Open Coverage Report +$ open coverage/index.html +``` + +--- + +## Working with Translations 🌐 + +This project relies on [flutter_localizations][flutter_localizations_link] and follows the [official internationalization guide for Flutter][internationalization_link]. + +### Adding Strings + +1. To add a new localizable string, open the `app_en.arb` file at `lib/l10n/arb/app_en.arb`. + +```arb +{ + "@@locale": "en", + "counterAppBarTitle": "Counter", + "@counterAppBarTitle": { + "description": "Text shown in the AppBar of the Counter Page" + } +} +``` + +2. Then add a new key/value and description + +```arb +{ + "@@locale": "en", + "counterAppBarTitle": "Counter", + "@counterAppBarTitle": { + "description": "Text shown in the AppBar of the Counter Page" + }, + "helloWorld": "Hello World", + "@helloWorld": { + "description": "Hello World Text" + } +} +``` + +3. Use the new string + +```dart +import 'package:todo/l10n/l10n.dart'; + +@override +Widget build(BuildContext context) { + final l10n = context.l10n; + return Text(l10n.helloWorld); +} +``` + +### Adding Supported Locales + +Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale. + +```xml + ... + + CFBundleLocalizations + + en + es + + + ... +``` + +### Adding Translations + +1. For each supported locale, add a new ARB file in `lib/l10n/arb`. + +``` +├── l10n +│ ├── arb +│ │ ├── app_en.arb +│ │ └── app_es.arb +``` + +2. Add the translated strings to each `.arb` file: + +`app_en.arb` + +```arb +{ + "@@locale": "en", + "counterAppBarTitle": "Counter", + "@counterAppBarTitle": { + "description": "Text shown in the AppBar of the Counter Page" + } +} +``` + +`app_es.arb` + +```arb +{ + "@@locale": "es", + "counterAppBarTitle": "Contador", + "@counterAppBarTitle": { + "description": "Texto mostrado en la AppBar de la página del contador" + } +} +``` + +### Generating Translations + +To use the latest translations changes, you will need to generate them: + +1. Generate localizations for the current project: + +```sh +flutter gen-l10n --arb-dir="lib/l10n/arb" +``` + +Alternatively, run `flutter run` and code generation will take place automatically. + +[coverage_badge]: coverage_badge.svg +[flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html +[internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis diff --git a/todo/analysis_options.yaml b/todo/analysis_options.yaml new file mode 100644 index 0000000..5105b91 --- /dev/null +++ b/todo/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" + +linter: + rules: + public_member_api_docs: false diff --git a/todo/android/.gitignore b/todo/android/.gitignore new file mode 100644 index 0000000..d6aa291 --- /dev/null +++ b/todo/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/todo/android/app/build.gradle b/todo/android/app/build.gradle new file mode 100644 index 0000000..86d1433 --- /dev/null +++ b/todo/android/app/build.gradle @@ -0,0 +1,115 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + namespace "com.example.todo" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.todo" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + signingConfigs { + if (System.getenv("ANDROID_KEYSTORE_PATH")) { + release { + storeFile file(System.getenv("ANDROID_KEYSTORE_PATH")) + keyAlias System.getenv("ANDROID_KEYSTORE_ALIAS") + keyPassword System.getenv("ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD") + storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") + } + } else { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + } + + flavorDimensions "default" + productFlavors { + production { + dimension "default" + applicationIdSuffix "" + manifestPlaceholders = [appName: "Todo"] + } + staging { + dimension "default" + applicationIdSuffix ".stg" + manifestPlaceholders = [appName: "[STG] Todo"] + } + development { + dimension "default" + applicationIdSuffix ".dev" + manifestPlaceholders = [appName: "[DEV] Todo"] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + debug { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10" +} diff --git a/todo/android/app/src/debug/AndroidManifest.xml b/todo/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..42ff08b --- /dev/null +++ b/todo/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/todo/android/app/src/development/res/drawable-v21/launch_background.xml b/todo/android/app/src/development/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/todo/android/app/src/development/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/development/res/drawable/launch_background.xml b/todo/android/app/src/development/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/todo/android/app/src/development/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/todo/android/app/src/main/AndroidManifest.xml b/todo/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..49db33d --- /dev/null +++ b/todo/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/todo/android/app/src/main/kotlin/com/example/MainActivity.kt b/todo/android/app/src/main/kotlin/com/example/MainActivity.kt new file mode 100644 index 0000000..9b5d2b3 --- /dev/null +++ b/todo/android/app/src/main/kotlin/com/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.todo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/todo/android/app/src/main/res/drawable-v21/launch_background.xml b/todo/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/todo/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/main/res/drawable/launch_background.xml b/todo/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/todo/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/todo/android/app/src/main/res/values-night/styles.xml b/todo/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/todo/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/todo/android/app/src/main/res/values/styles.xml b/todo/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/todo/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/todo/android/app/src/profile/AndroidManifest.xml b/todo/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..42ff08b --- /dev/null +++ b/todo/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/todo/android/app/src/staging/res/drawable-v21/launch_background.xml b/todo/android/app/src/staging/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/todo/android/app/src/staging/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/staging/res/drawable/launch_background.xml b/todo/android/app/src/staging/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/todo/android/app/src/staging/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/todo/android/build.gradle b/todo/android/build.gradle new file mode 100644 index 0000000..bc157bd --- /dev/null +++ b/todo/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/todo/android/gradle.properties b/todo/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/todo/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/todo/android/gradle/wrapper/gradle-wrapper.properties b/todo/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b66533 --- /dev/null +++ b/todo/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/todo/android/gradlew b/todo/android/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/todo/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/todo/android/gradlew.bat b/todo/android/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/todo/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/todo/android/settings.gradle b/todo/android/settings.gradle new file mode 100644 index 0000000..1d6d19b --- /dev/null +++ b/todo/android/settings.gradle @@ -0,0 +1,26 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/todo/app-keys.copy.json b/todo/app-keys.copy.json new file mode 100644 index 0000000..12a8fc2 --- /dev/null +++ b/todo/app-keys.copy.json @@ -0,0 +1,4 @@ +{ + "KEY_ANDROID": "", + "KEY_IOS": "" +} \ No newline at end of file diff --git a/todo/coverage_badge.svg b/todo/coverage_badge.svg new file mode 100644 index 0000000..88bfadf --- /dev/null +++ b/todo/coverage_badge.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + coverage + coverage + 100% + 100% + + \ No newline at end of file diff --git a/todo/ios/.gitignore b/todo/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/todo/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/todo/ios/Flutter/AppFrameworkInfo.plist b/todo/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..4f8d4d2 --- /dev/null +++ b/todo/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/todo/ios/Flutter/Debug.xcconfig b/todo/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/todo/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/todo/ios/Flutter/Release.xcconfig b/todo/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/todo/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/todo/ios/Runner.xcodeproj/project.pbxproj b/todo/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f982601 --- /dev/null +++ b/todo/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,1182 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = NO; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3C9C55192B07AC99000E5FCD /* Debug-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-production"; + }; + 3C9C551A2B07AC99000E5FCD /* Debug-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-production"; + }; + 3C9C551B2B07AC99000E5FCD /* Debug-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-production"; + }; + 3C9C551C2B07ACA4000E5FCD /* Release-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-production"; + }; + 3C9C551D2B07ACA4000E5FCD /* Release-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-production"; + }; + 3C9C551E2B07ACA4000E5FCD /* Release-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-production"; + }; + 3C9C55222B07ACBD000E5FCD /* Profile-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-production"; + }; + 3C9C55232B07ACBD000E5FCD /* Profile-production */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-production"; + }; + 3C9C55242B07ACBD000E5FCD /* Profile-production */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-production"; + }; + 3C9C55252B07ACE7000E5FCD /* Debug-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-development"; + }; + 3C9C55262B07ACE7000E5FCD /* Debug-development */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[DEV] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-development"; + }; + 3C9C55272B07ACE7000E5FCD /* Debug-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-development"; + }; + 3C9C552B2B07AD09000E5FCD /* Profile-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-development"; + }; + 3C9C552C2B07AD09000E5FCD /* Profile-development */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[DEV] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-development"; + }; + 3C9C552D2B07AD09000E5FCD /* Profile-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-development"; + }; + 3C9C552E2B07AD1B000E5FCD /* Release-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-development"; + }; + 3C9C552F2B07AD1B000E5FCD /* Release-development */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[DEV] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-development"; + }; + 3C9C55302B07AD1B000E5FCD /* Release-development */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-development"; + }; + 3C9C55312B07AD26000E5FCD /* Debug-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-staging"; + }; + 3C9C55322B07AD26000E5FCD /* Debug-staging */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[STG] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-staging"; + }; + 3C9C55332B07AD26000E5FCD /* Debug-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-staging"; + }; + 3C9C55342B07AD41000E5FCD /* Release-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-staging"; + }; + 3C9C55352B07AD41000E5FCD /* Release-staging */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[STG] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-staging"; + }; + 3C9C55362B07AD41000E5FCD /* Release-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-staging"; + }; + 3C9C55372B07AD4B000E5FCD /* Profile-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-staging"; + }; + 3C9C55382B07AD4B000E5FCD /* Profile-staging */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FLAVOR_APP_NAME = "[STG] Todo"; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PACKAGE_CONFIG = .dart_tool/package_config.json; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-staging"; + }; + 3C9C55392B07AD4B000E5FCD /* Profile-staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-staging"; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C9C551B2B07AC99000E5FCD /* Debug-production */, + 3C9C55332B07AD26000E5FCD /* Debug-staging */, + 3C9C55272B07ACE7000E5FCD /* Debug-development */, + 3C9C551E2B07ACA4000E5FCD /* Release-production */, + 3C9C55362B07AD41000E5FCD /* Release-staging */, + 3C9C55302B07AD1B000E5FCD /* Release-development */, + 3C9C55242B07ACBD000E5FCD /* Profile-production */, + 3C9C55392B07AD4B000E5FCD /* Profile-staging */, + 3C9C552D2B07AD09000E5FCD /* Profile-development */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-production"; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C9C55192B07AC99000E5FCD /* Debug-production */, + 3C9C55312B07AD26000E5FCD /* Debug-staging */, + 3C9C55252B07ACE7000E5FCD /* Debug-development */, + 3C9C551C2B07ACA4000E5FCD /* Release-production */, + 3C9C55342B07AD41000E5FCD /* Release-staging */, + 3C9C552E2B07AD1B000E5FCD /* Release-development */, + 3C9C55222B07ACBD000E5FCD /* Profile-production */, + 3C9C55372B07AD4B000E5FCD /* Profile-staging */, + 3C9C552B2B07AD09000E5FCD /* Profile-development */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-production"; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3C9C551A2B07AC99000E5FCD /* Debug-production */, + 3C9C55322B07AD26000E5FCD /* Debug-staging */, + 3C9C55262B07ACE7000E5FCD /* Debug-development */, + 3C9C551D2B07ACA4000E5FCD /* Release-production */, + 3C9C55352B07AD41000E5FCD /* Release-staging */, + 3C9C552F2B07AD1B000E5FCD /* Release-development */, + 3C9C55232B07ACBD000E5FCD /* Profile-production */, + 3C9C55382B07AD4B000E5FCD /* Profile-staging */, + 3C9C552C2B07AD09000E5FCD /* Profile-development */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release-production"; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme new file mode 100644 index 0000000..06da47c --- /dev/null +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme new file mode 100644 index 0000000..b59fb06 --- /dev/null +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme new file mode 100644 index 0000000..6e3b252 --- /dev/null +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner.xcworkspace/contents.xcworkspacedata b/todo/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/todo/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/todo/ios/Runner/AppDelegate.swift b/todo/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/todo/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..6564da0 --- /dev/null +++ b/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner/Base.lproj/Main.storyboard b/todo/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/todo/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/ios/Runner/Info.plist b/todo/ios/Runner/Info.plist new file mode 100644 index 0000000..25236cf --- /dev/null +++ b/todo/ios/Runner/Info.plist @@ -0,0 +1,56 @@ + + + + + CFBundleLocalizations + + en + es + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $(FLAVOR_APP_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(FLAVOR_APP_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/todo/ios/Runner/Runner-Bridging-Header.h b/todo/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/todo/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/todo/ios/RunnerTests/RunnerTests.swift b/todo/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/todo/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/todo/l10n.yaml b/todo/l10n.yaml new file mode 100644 index 0000000..6f72a55 --- /dev/null +++ b/todo/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n/arb +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +nullable-getter: false diff --git a/todo/lib/app/app.dart b/todo/lib/app/app.dart new file mode 100644 index 0000000..5811d3c --- /dev/null +++ b/todo/lib/app/app.dart @@ -0,0 +1,4 @@ +export 'bloc/app_bloc.dart'; +export 'extensions/extensions.dart'; +export 'router/app_router.dart'; +export 'view/app.dart'; diff --git a/todo/lib/app/bloc/app_bloc.dart b/todo/lib/app/bloc/app_bloc.dart new file mode 100644 index 0000000..a485704 --- /dev/null +++ b/todo/lib/app/bloc/app_bloc.dart @@ -0,0 +1,138 @@ +import 'dart:async'; + +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'app_event.dart'; +part 'app_state.dart'; + +class AppBloc extends Bloc { + AppBloc({ + required User? user, + required UserRepository userRepository, + required AppConfigRepository appConfigRepository, + }) : _userRepository = userRepository, + super( + user == null + ? const AppState.unauthenticated( + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ) + : AppState.authenticated( + user: user, + forceUpgrade: const ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ) { + on(_onAppUserChanged); + on( + _onAppDownForMaintenanceStatusChanged, + ); + on(_onAppLogoutRequested); + on(_onAppForceUpgradeStatusChanged); + _userSubscription = _userRepository.user.listen(_onUser); + _forceUpgradeSubscription = appConfigRepository + .isForceUpgradeRequired() + .listen(_forceUpgradeStatusChanged); + _isDownForMaintenanceSubscription = appConfigRepository + .isDownForMaintenance() + .listen(_downForMaintenanceStatusChanged); + } + + final UserRepository _userRepository; + late final StreamSubscription _userSubscription; + late StreamSubscription _forceUpgradeSubscription; + late StreamSubscription _isDownForMaintenanceSubscription; + + void _onUser(User? user) { + add(AppUserChanged(user: user)); + } + + void _downForMaintenanceStatusChanged(bool isDownForMaintenance) { + add( + AppDownForMaintenanceStatusChanged( + isDownForMaintenance: isDownForMaintenance, + ), + ); + } + + void _forceUpgradeStatusChanged(ForceUpgrade forceUpgrade) { + add(AppForceUpgradeStatusChanged(forceUpgrade)); + } + + Future _onAppUserChanged( + AppUserChanged event, + Emitter emit, + ) async { + final user = event.user; + if (user == null) { + emit( + AppState.unauthenticated( + forceUpgrade: state.forceUpgrade, + isDownForMaintenance: state.isDownForMaintenance, + ), + ); + } else { + emit( + AppState.authenticated( + user: user, + forceUpgrade: state.forceUpgrade, + isDownForMaintenance: state.isDownForMaintenance, + ), + ); + } + } + + void _onAppDownForMaintenanceStatusChanged( + AppDownForMaintenanceStatusChanged event, + Emitter emit, + ) { + final isDownForMaintenance = event.isDownForMaintenance; + if (isDownForMaintenance) { + emit( + AppState.downForMaintenance( + user: state.user, + forceUpgrade: state.forceUpgrade, + isDownForMaintenance: isDownForMaintenance, + ), + ); + } + } + + Future _onAppForceUpgradeStatusChanged( + AppForceUpgradeStatusChanged event, + Emitter emit, + ) async { + final forceUpgrade = event.forceUpgrade; + if (forceUpgrade.isUpgradeRequired) { + emit( + state.copyWith( + status: AppStatus.forceUpgrade, + forceUpgrade: forceUpgrade, + ), + ); + } + } + + Future _onAppLogoutRequested( + AppLogoutRequested event, + Emitter emit, + ) async { + if (state.user == null) return; + await _userRepository.signOut(); + emit( + state.copyWith(status: AppStatus.unauthenticated), + ); + } + + @override + Future close() { + unawaited(_userSubscription.cancel()); + _forceUpgradeSubscription.cancel(); + _isDownForMaintenanceSubscription.cancel(); + return super.close(); + } +} diff --git a/todo/lib/app/bloc/app_event.dart b/todo/lib/app/bloc/app_event.dart new file mode 100644 index 0000000..794b08d --- /dev/null +++ b/todo/lib/app/bloc/app_event.dart @@ -0,0 +1,44 @@ +part of 'app_bloc.dart'; + +abstract class AppEvent extends Equatable { + const AppEvent(); +} + +class AppUserChanged extends AppEvent { + @visibleForTesting + const AppUserChanged({required this.user}); + + final User? user; + + @override + List get props => [user]; +} + +class AppDownForMaintenanceStatusChanged extends AppEvent { + @visibleForTesting + const AppDownForMaintenanceStatusChanged({ + required this.isDownForMaintenance, + }); + + final bool isDownForMaintenance; + + @override + List get props => [isDownForMaintenance]; +} + +class AppForceUpgradeStatusChanged extends AppEvent { + @visibleForTesting + const AppForceUpgradeStatusChanged(this.forceUpgrade); + + final ForceUpgrade forceUpgrade; + + @override + List get props => [forceUpgrade]; +} + +class AppLogoutRequested extends AppEvent { + const AppLogoutRequested(); + + @override + List get props => []; +} diff --git a/todo/lib/app/bloc/app_state.dart b/todo/lib/app/bloc/app_state.dart new file mode 100644 index 0000000..e58bf80 --- /dev/null +++ b/todo/lib/app/bloc/app_state.dart @@ -0,0 +1,74 @@ +part of 'app_bloc.dart'; + +enum AppStatus { + downForMaintenance, + authenticated, + unauthenticated, + forceUpgrade; +} + +class AppState extends Equatable { + const AppState({ + required this.user, + required this.status, + this.forceUpgrade = const ForceUpgrade(isUpgradeRequired: false), + this.isDownForMaintenance = false, + }); + + const AppState.authenticated({ + required User user, + required ForceUpgrade? forceUpgrade, + required bool? isDownForMaintenance, + }) : this( + user: user, + status: AppStatus.authenticated, + forceUpgrade: forceUpgrade, + isDownForMaintenance: isDownForMaintenance, + ); + + const AppState.unauthenticated({ + required ForceUpgrade? forceUpgrade, + required bool? isDownForMaintenance, + }) : this( + user: null, + status: AppStatus.unauthenticated, + forceUpgrade: forceUpgrade, + isDownForMaintenance: isDownForMaintenance, + ); + + const AppState.downForMaintenance({ + required ForceUpgrade? forceUpgrade, + required bool isDownForMaintenance, + User? user, + }) : this( + user: user, + status: AppStatus.downForMaintenance, + forceUpgrade: forceUpgrade, + isDownForMaintenance: isDownForMaintenance, + ); + + final User? user; + final AppStatus status; + final ForceUpgrade? forceUpgrade; + final bool? isDownForMaintenance; + + @override + List get props => [ + user, + status, + ]; + + AppState copyWith({ + User? user, + AppStatus? status, + ForceUpgrade? forceUpgrade, + bool? isDownForMaintenance, + }) { + return AppState( + user: user ?? this.user, + status: status ?? this.status, + forceUpgrade: forceUpgrade ?? this.forceUpgrade, + isDownForMaintenance: isDownForMaintenance ?? this.isDownForMaintenance, + ); + } +} diff --git a/todo/lib/app/constants/constants.dart b/todo/lib/app/constants/constants.dart new file mode 100644 index 0000000..246ee50 --- /dev/null +++ b/todo/lib/app/constants/constants.dart @@ -0,0 +1 @@ +export 'navigation_keys.dart'; diff --git a/todo/lib/app/constants/navigation_keys.dart b/todo/lib/app/constants/navigation_keys.dart new file mode 100644 index 0000000..94fd2eb --- /dev/null +++ b/todo/lib/app/constants/navigation_keys.dart @@ -0,0 +1,3 @@ +import 'package:flutter/widgets.dart'; + +final rootNavigatorKey = GlobalKey(); diff --git a/todo/lib/app/extensions/build_context.dart b/todo/lib/app/extensions/build_context.dart new file mode 100644 index 0000000..7995834 --- /dev/null +++ b/todo/lib/app/extensions/build_context.dart @@ -0,0 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +extension BuildContextX on BuildContext { + User? get user => read().state.user; +} diff --git a/todo/lib/app/extensions/extensions.dart b/todo/lib/app/extensions/extensions.dart new file mode 100644 index 0000000..b39f3ac --- /dev/null +++ b/todo/lib/app/extensions/extensions.dart @@ -0,0 +1 @@ +export 'build_context.dart'; diff --git a/todo/lib/app/router/app_router.dart b/todo/lib/app/router/app_router.dart new file mode 100644 index 0000000..2a764de --- /dev/null +++ b/todo/lib/app/router/app_router.dart @@ -0,0 +1,101 @@ +import 'package:todo/app/constants/navigation_keys.dart'; +import 'package:todo/app/router/router.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; +import 'package:todo/authentication/login/login.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; +import 'package:todo/force_upgrade/force_upgrade.dart'; +import 'package:todo/home/home.dart'; +import 'package:todo/settings/settings.dart'; +import 'package:go_router/go_router.dart'; + +export 'auth_stream_scope.dart'; + +class AppRouter { + /// Only routes that are accessible to unauthenticated users + static const onlyUnauthenticatedUserRoutes = [ + SignUpPage.path, + LoginPage.path, + ForgotPasswordPage.path, + ]; + + /// Only routes that are accessible for authenticated users + static const onlyAuthenticatedUserRoutes = [ + HomePage.path, + SettingsPage.path, + ]; + + static GoRouter router() { + return GoRouter( + navigatorKey: rootNavigatorKey, + initialLocation: LoginPage.path, + redirect: (context, state) { + final path = state.uri.path; + final isAuthenticated = AppStatusStreamScope.of(context).isSignedIn; + final isUnauthenticated = AppStatusStreamScope.of(context).isSignedOut; + final isDownForMaintenance = + AppStatusStreamScope.of(context).isDownForMaintenance; + final forceUpgrade = AppStatusStreamScope.of(context).forceUpgrade; + if (isDownForMaintenance) { + return DownForMaintenancePage.path; + } + if (forceUpgrade) { + return ForceUpgradePage.path; + } + if (onlyUnauthenticatedUserRoutes.contains(path) && isAuthenticated) { + return HomePage.path; + } + if (onlyAuthenticatedUserRoutes.contains(path) && isUnauthenticated) { + return LoginPage.path; + } + return null; + }, + routes: [ + NavigationBarRouter(), + GoRoute( + path: SignUpPage.path, + pageBuilder: (context, state) { + return const SignUpPage(); + }, + ), + GoRoute( + path: LoginPage.path, + pageBuilder: (context, state) { + return const LoginPage(); + }, + ), + GoRoute( + path: DeleteAccountPage.path, + pageBuilder: (context, state) { + return const DeleteAccountPage(); + }, + ), + GoRoute( + path: ForgotPasswordPage.path, + pageBuilder: (context, state) { + return ForgotPasswordPage(); + }, + ), + GoRoute( + path: DownForMaintenancePage.path, + pageBuilder: (context, state) { + return const DownForMaintenancePage(); + }, + ), + GoRoute( + path: ForceUpgradePage.path, + pageBuilder: (context, state) { + return const ForceUpgradePage(); + }, + ), + GoRoute( + path: SettingsPage.path, + pageBuilder: (context, state) { + return const SettingsPage(); + }, + ), + ], + ); + } +} diff --git a/todo/lib/app/router/auth_stream_scope.dart b/todo/lib/app/router/auth_stream_scope.dart new file mode 100644 index 0000000..8dbcd11 --- /dev/null +++ b/todo/lib/app/router/auth_stream_scope.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:todo/app/app.dart'; +import 'package:flutter/material.dart'; + +class AppStatusStreamScope extends InheritedNotifier { + AppStatusStreamScope({ + required AppBloc appBloc, + required super.child, + super.key, + }) : super(notifier: AppStatusStream(appBloc, appBloc.stream)); + + static AppStatusStream of(BuildContext ctx) => + ctx.dependOnInheritedWidgetOfExactType()!.notifier!; +} + +class AppStatusStream extends ChangeNotifier { + AppStatusStream( + AppBloc bloc, + Stream stream, + ) : _bloc = bloc { + _subscription = stream.asBroadcastStream().listen((_) => notifyListeners()); + } + + final AppBloc _bloc; + late final StreamSubscription _subscription; + + bool get isSignedIn => _bloc.state.status == AppStatus.authenticated; + bool get isSignedOut => _bloc.state.status == AppStatus.unauthenticated; + bool get isDownForMaintenance => + _bloc.state.status == AppStatus.downForMaintenance; + bool get forceUpgrade => _bloc.state.status == AppStatus.forceUpgrade; + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} diff --git a/todo/lib/app/router/navigation_bar_router.dart b/todo/lib/app/router/navigation_bar_router.dart new file mode 100644 index 0000000..fb3cb0c --- /dev/null +++ b/todo/lib/app/router/navigation_bar_router.dart @@ -0,0 +1,34 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:todo/home/home.dart'; +import 'package:todo/settings/view/view.dart'; + +class NavigationBarRouter extends ShellRoute { + NavigationBarRouter() + : super( + pageBuilder: (context, state, child) { + int? index; + if (state.fullPath == HomePage.path) { + index = 0; + } else if (state.fullPath == SettingsPage.path) { + index = 1; + } + + return MainScreenPageBuilder.route(child, index); + }, + routes: [ + GoRoute( + path: HomePage.path, + pageBuilder: (context, state) { + return const HomePage(); + }, + ), + GoRoute( + path: SettingsPage.path, + pageBuilder: (context, state) { + return const SettingsPage(); + }, + ), + ], + ); +} diff --git a/todo/lib/app/router/router.dart b/todo/lib/app/router/router.dart new file mode 100644 index 0000000..c290ccd --- /dev/null +++ b/todo/lib/app/router/router.dart @@ -0,0 +1,3 @@ +export 'app_router.dart'; +export 'auth_stream_scope.dart'; +export 'navigation_bar_router.dart'; diff --git a/todo/lib/app/view/app.dart b/todo/lib/app/view/app.dart new file mode 100644 index 0000000..3d5cdb8 --- /dev/null +++ b/todo/lib/app/view/app.dart @@ -0,0 +1,69 @@ +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/app/app.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:todo_ui/todo_ui.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:user_repository/user_repository.dart'; + +class App extends StatelessWidget { + App({ + required UserRepository userRepository, + required User? user, + required AppConfigRepository appConfigRepository, + super.key, + }) : _user = user, + _appConfigRepository = appConfigRepository, + _routerConfig = AppRouter.router(), + _userRepository = userRepository; + + final User? _user; + final UserRepository _userRepository; + final GoRouter _routerConfig; + final AppConfigRepository _appConfigRepository; + + @override + Widget build(BuildContext context) { + return MultiRepositoryProvider( + providers: [ + RepositoryProvider.value(value: _userRepository), + RepositoryProvider.value(value: _appConfigRepository), + ], + child: BlocProvider( + create: (context) => AppBloc( + userRepository: _userRepository, + user: _user, + appConfigRepository: _appConfigRepository, + ), + child: AppView( + routerConfig: _routerConfig, + ), + ), + ); + } +} + +class AppView extends StatelessWidget { + const AppView({ + required GoRouter routerConfig, + super.key, + }) : _routerConfig = routerConfig; + + final GoRouter _routerConfig; + + @override + Widget build(BuildContext context) { + return AppStatusStreamScope( + appBloc: context.read(), + child: MaterialApp.router( + theme: UITheme().lightTheme, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + routerConfig: _routerConfig, + title: 'todo', + ), + ); + } +} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart b/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart new file mode 100644 index 0000000..6bc3635 --- /dev/null +++ b/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart @@ -0,0 +1,84 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'delete_account_event.dart'; +part 'delete_account_state.dart'; + +class DeleteAccountBloc extends Bloc { + DeleteAccountBloc({ + required UserRepository userRepository, + }) : _userRepository = userRepository, + super(const DeleteAccountState.initial()) { + on(_onDeleteAccountRequested); + on(_onEmailChanged); + on(_onPasswordChanged); + on(_onDeleteAccountSubmitted); + on(_onDeleteAccountResetted); + } + + // ignore: unused_field + final UserRepository _userRepository; + + FutureOr _onDeleteAccountRequested( + DeleteAccountRequested event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: DeleteAccountStatus.loading)); + await _userRepository.deleteAccount(); + emit(state.copyWith(status: DeleteAccountStatus.reauthenticated)); + } on UserReAuthenticateFailure { + emit( + state.copyWith( + status: DeleteAccountStatus.reauthFailure, + ), + ); + } on Exception { + emit( + state.copyWith( + status: DeleteAccountStatus.failure, + ), + ); + } + } + + FutureOr _onEmailChanged( + DeleteAccountEmailChanged event, + Emitter emit, + ) { + emit(state.copyWith(email: Email.dirty(event.email))); + } + + FutureOr _onPasswordChanged( + DeleteAccountPasswordChanged event, + Emitter emit, + ) { + emit(state.copyWith(password: Password.dirty(event.password))); + } + + FutureOr _onDeleteAccountSubmitted( + DeleteAccountSubmitted event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: DeleteAccountStatus.loading)); + await _userRepository.deleteAccount(); + emit(state.copyWith(status: DeleteAccountStatus.success)); + } on UserDeleteAccountFailure { + emit(state.copyWith(status: DeleteAccountStatus.deleteAccountFailure)); + } on Exception { + emit(state.copyWith(status: DeleteAccountStatus.failure)); + } + } + + FutureOr _onDeleteAccountResetted( + DeleteAccountResetted event, + Emitter emit, + ) { + emit(state.copyWith(status: DeleteAccountStatus.initial)); + } +} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_event.dart b/todo/lib/authentication/delete_account/bloc/delete_account_event.dart new file mode 100644 index 0000000..dcd1e1b --- /dev/null +++ b/todo/lib/authentication/delete_account/bloc/delete_account_event.dart @@ -0,0 +1,44 @@ +part of 'delete_account_bloc.dart'; + +abstract class DeleteAccountEvent extends Equatable { + const DeleteAccountEvent(); +} + +class DeleteAccountRequested extends DeleteAccountEvent { + const DeleteAccountRequested(); + + @override + List get props => []; +} + +class DeleteAccountSubmitted extends DeleteAccountEvent { + const DeleteAccountSubmitted(); + + @override + List get props => []; +} + +class DeleteAccountEmailChanged extends DeleteAccountEvent { + const DeleteAccountEmailChanged(this.email); + + final String email; + + @override + List get props => [email]; +} + +class DeleteAccountPasswordChanged extends DeleteAccountEvent { + const DeleteAccountPasswordChanged(this.password); + + final String password; + + @override + List get props => [password]; +} + +class DeleteAccountResetted extends DeleteAccountEvent { + const DeleteAccountResetted(); + + @override + List get props => []; +} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_state.dart b/todo/lib/authentication/delete_account/bloc/delete_account_state.dart new file mode 100644 index 0000000..02011a7 --- /dev/null +++ b/todo/lib/authentication/delete_account/bloc/delete_account_state.dart @@ -0,0 +1,51 @@ +part of 'delete_account_bloc.dart'; + +enum DeleteAccountStatus { + initial, + loading, + success, + reauthenticated, + reauthFailure, + deleteAccountFailure, + failure; +} + +class DeleteAccountState extends Equatable { + const DeleteAccountState({ + required this.status, + required this.email, + required this.password, + }); + + const DeleteAccountState.initial() + : this( + status: DeleteAccountStatus.initial, + email: const Email.pure(), + password: const Password.pure(), + ); + + final DeleteAccountStatus status; + final Email email; + final Password password; + + DeleteAccountState copyWith({ + DeleteAccountStatus? status, + Email? email, + Password? password, + }) { + return DeleteAccountState( + status: status ?? this.status, + email: email ?? this.email, + password: password ?? this.password, + ); + } + + bool get valid => Formz.validate([email, password]); + + @override + List get props => [ + status, + email, + password, + ]; +} diff --git a/todo/lib/authentication/delete_account/delete_account.dart b/todo/lib/authentication/delete_account/delete_account.dart new file mode 100644 index 0000000..690bb3d --- /dev/null +++ b/todo/lib/authentication/delete_account/delete_account.dart @@ -0,0 +1,2 @@ +export 'bloc/delete_account_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/authentication/delete_account/view/delete_account_page.dart b/todo/lib/authentication/delete_account/view/delete_account_page.dart new file mode 100644 index 0000000..3d54edc --- /dev/null +++ b/todo/lib/authentication/delete_account/view/delete_account_page.dart @@ -0,0 +1,26 @@ +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:user_repository/user_repository.dart'; + +class DeleteAccountPage extends Page { + const DeleteAccountPage(); + + static const path = '/delete-account'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + fullscreenDialog: true, + settings: this, + builder: (ctx) { + return BlocProvider( + create: (_) => DeleteAccountBloc( + userRepository: ctx.read(), + ), + child: const DeleteAccountView(), + ); + }, + ); + } +} diff --git a/todo/lib/authentication/delete_account/view/delete_account_view.dart b/todo/lib/authentication/delete_account/view/delete_account_view.dart new file mode 100644 index 0000000..ea570fd --- /dev/null +++ b/todo/lib/authentication/delete_account/view/delete_account_view.dart @@ -0,0 +1,112 @@ +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo_ui/todo_ui.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DeleteAccountView extends StatelessWidget { + const DeleteAccountView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Scaffold( + appBar: AppBar( + title: Text(l10n.deleteAccount), + leading: const BackButton(), + ), + body: const SafeArea( + child: Padding( + padding: EdgeInsets.all(UISpacing.xlg), + child: DeleteAccountForm(), + ), + ), + ); + } +} + +class DeleteAccountForm extends StatelessWidget { + const DeleteAccountForm({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _EmailField(), + SizedBox(height: UISpacing.md), + _PasswordField(), + SizedBox(height: UISpacing.md), + _DeleteAccountButton(), + ], + ); + } +} + +class _PasswordField extends StatefulWidget { + const _PasswordField(); + + @override + State<_PasswordField> createState() => _PasswordFieldState(); +} + +class _PasswordFieldState extends State<_PasswordField> { + @override + Widget build(BuildContext context) { + final password = context.select( + (DeleteAccountBloc bloc) => bloc.state.password, + ); + final initialValue = password.value; + return UITextField.passwordTextField( + key: const Key('DeleteAccountPassword'), + initialValue: initialValue, + onChanged: (password) { + context.read().add( + DeleteAccountPasswordChanged(password), + ); + }, + ); + } +} + +class _EmailField extends StatelessWidget { + const _EmailField(); + + @override + Widget build(BuildContext context) { + final email = context.read().state.email; + final initialValue = email.value; + return UITextField.emailTextField( + key: const Key('DeleteAccountEmail'), + initialValue: initialValue, + onChanged: (email) { + context.read().add( + DeleteAccountEmailChanged(email), + ); + }, + ); + } +} + +class _DeleteAccountButton extends StatelessWidget { + const _DeleteAccountButton(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final isValid = + context.select((DeleteAccountBloc bloc) => bloc.state.valid); + + return UIOutlinedButton.primary( + text: l10n.deleteAccount, + onPressed: isValid + ? () { + context.read().add( + const DeleteAccountSubmitted(), + ); + } + : null, + ); + } +} diff --git a/todo/lib/authentication/delete_account/view/view.dart b/todo/lib/authentication/delete_account/view/view.dart new file mode 100644 index 0000000..304ae6e --- /dev/null +++ b/todo/lib/authentication/delete_account/view/view.dart @@ -0,0 +1,2 @@ +export 'delete_account_page.dart'; +export 'delete_account_view.dart'; diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart new file mode 100644 index 0000000..edc6fcd --- /dev/null +++ b/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'forgot_password_event.dart'; +part 'forgot_password_state.dart'; + +class ForgotPasswordBloc + extends Bloc { + ForgotPasswordBloc({required UserRepository userRepository}) + : _userRepository = userRepository, + super(const ForgotPasswordState.initial()) { + on(_onEmailChanged); + on(_onSubmitted); + } + final UserRepository _userRepository; + + FutureOr _onEmailChanged( + ForgotPasswordEmailChanged event, + Emitter emit, + ) { + emit(state.copyWith(email: Email.dirty(event.email))); + } + + FutureOr _onSubmitted( + ForgotPasswordSubmitted event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: ForgotPasswordStatus.loading)); + await _userRepository.forgotPassword(email: state.email.value); + emit(state.copyWith(status: ForgotPasswordStatus.success)); + } on Exception { + emit(state.copyWith(status: ForgotPasswordStatus.error)); + } + } +} diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart new file mode 100644 index 0000000..7d8cda9 --- /dev/null +++ b/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart @@ -0,0 +1,21 @@ +part of 'forgot_password_bloc.dart'; + +abstract class ForgotPasswordEvent extends Equatable { + const ForgotPasswordEvent(); +} + +class ForgotPasswordSubmitted extends ForgotPasswordEvent { + const ForgotPasswordSubmitted(); + + @override + List get props => []; +} + +class ForgotPasswordEmailChanged extends ForgotPasswordEvent { + const ForgotPasswordEmailChanged(this.email); + + final String email; + + @override + List get props => [email]; +} diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart new file mode 100644 index 0000000..65c650b --- /dev/null +++ b/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart @@ -0,0 +1,49 @@ +part of 'forgot_password_bloc.dart'; + +enum ForgotPasswordStatus { + initial, + loading, + loaded, + error, + success; + + bool get isInitial => this == ForgotPasswordStatus.initial; + bool get isLoading => this == ForgotPasswordStatus.loading; + bool get isLoaded => this == ForgotPasswordStatus.loaded; + bool get isSuccess => this == ForgotPasswordStatus.success; + bool get isError => this == ForgotPasswordStatus.error; +} + +class ForgotPasswordState extends Equatable { + const ForgotPasswordState({ + required this.status, + required this.email, + }); + + const ForgotPasswordState.initial() + : this( + status: ForgotPasswordStatus.initial, + email: const Email.pure(), + ); + + final ForgotPasswordStatus status; + final Email email; + + bool get valid => Formz.validate([email]); + + ForgotPasswordState copyWith({ + ForgotPasswordStatus? status, + Email? email, + }) { + return ForgotPasswordState( + status: status ?? this.status, + email: email ?? this.email, + ); + } + + @override + List get props => [ + status, + email, + ]; +} diff --git a/todo/lib/authentication/forgot_password/forgot_password.dart b/todo/lib/authentication/forgot_password/forgot_password.dart new file mode 100644 index 0000000..1f3ee54 --- /dev/null +++ b/todo/lib/authentication/forgot_password/forgot_password.dart @@ -0,0 +1,2 @@ +export 'bloc/forgot_password_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/authentication/forgot_password/view/forgot_password_page.dart b/todo/lib/authentication/forgot_password/view/forgot_password_page.dart new file mode 100644 index 0000000..65b26ae --- /dev/null +++ b/todo/lib/authentication/forgot_password/view/forgot_password_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; + +class ForgotPasswordPage extends Page { + static const path = '/forgot_password'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + fullscreenDialog: true, + settings: this, + builder: (ctx) { + return BlocProvider( + create: (_) => ForgotPasswordBloc( + userRepository: context.read(), + ), + child: const ForgotPasswordView(), + ); + }, + ); + } +} diff --git a/todo/lib/authentication/forgot_password/view/forgot_password_view.dart b/todo/lib/authentication/forgot_password/view/forgot_password_view.dart new file mode 100644 index 0000000..ce313a9 --- /dev/null +++ b/todo/lib/authentication/forgot_password/view/forgot_password_view.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; +import 'package:todo/l10n/l10n.dart'; + +class ForgotPasswordView extends StatelessWidget { + const ForgotPasswordView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + final l10n = context.l10n; + + if (state.status.isSuccess) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.resetSuccessful)), + ); + } + if (state.status.isError) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.unknownError)), + ); + } + }, + child: Scaffold( + appBar: AppBar( + leading: const BackButton(), + ), + body: const _ForgotPasswordForm(), + ), + ); + } +} + +class _ForgotPasswordForm extends StatelessWidget { + const _ForgotPasswordForm(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final status = + context.select((ForgotPasswordBloc bloc) => bloc.state.status); + + if (status == ForgotPasswordStatus.loading) { + return const Placeholder(); + } + return ListView( + padding: const EdgeInsets.all(24), + children: [ + Text(l10n.forgotPasswordDescription), + const SizedBox(height: 8), + const _EmailTextField(), + const SizedBox(height: 24), + const _ResetButton(), + const SizedBox(height: 24), + ], + ); + } +} + +class _ResetButton extends StatelessWidget { + const _ResetButton(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final valid = context.select((ForgotPasswordBloc bloc) => bloc.state.valid); + + return ElevatedButton( + onPressed: valid + ? () { + context + .read() + .add(const ForgotPasswordSubmitted()); + } + : null, + child: Text(l10n.reset), + ); + } +} + +class _EmailTextField extends StatelessWidget { + const _EmailTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final valid = context.select((ForgotPasswordBloc bloc) => bloc.state.valid); + + return TextField( + onChanged: (email) => context + .read() + .add(ForgotPasswordEmailChanged(email)), + decoration: InputDecoration( + hintText: l10n.email, + errorText: valid ? null : l10n.invalidEmail, + ), + keyboardType: TextInputType.emailAddress, + ); + } +} diff --git a/todo/lib/authentication/forgot_password/view/view.dart b/todo/lib/authentication/forgot_password/view/view.dart new file mode 100644 index 0000000..dc573aa --- /dev/null +++ b/todo/lib/authentication/forgot_password/view/view.dart @@ -0,0 +1,2 @@ +export 'forgot_password_page.dart'; +export 'forgot_password_view.dart'; diff --git a/todo/lib/authentication/login/bloc/login_bloc.dart b/todo/lib/authentication/login/bloc/login_bloc.dart new file mode 100644 index 0000000..85631af --- /dev/null +++ b/todo/lib/authentication/login/bloc/login_bloc.dart @@ -0,0 +1,62 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'login_event.dart'; +part 'login_state.dart'; + +class LoginBloc extends Bloc { + LoginBloc({required UserRepository userRepository}) + : _userRepository = userRepository, + super(const LoginState.initial()) { + on(_onEmailChanged); + on(_onPasswordChanged); + on(_onPasswordVisibilityChanged); + on( + _onLoginWithEmailAndPasswordRequested, + ); + } + + final UserRepository _userRepository; + + FutureOr _onLoginWithEmailAndPasswordRequested( + LoginWithEmailAndPasswordRequested event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); + await _userRepository.signIn( + email: state.email.value, + password: state.password.value, + ); + emit(state.copyWith(status: FormzSubmissionStatus.success)); + } on Exception { + emit(state.copyWith(status: FormzSubmissionStatus.failure)); + } + } + + FutureOr _onEmailChanged( + LoginEmailChanged event, + Emitter emit, + ) { + emit(state.copyWith(email: Email.dirty(event.email))); + } + + FutureOr _onPasswordChanged( + LoginPasswordChanged event, + Emitter emit, + ) { + emit(state.copyWith(password: Password.dirty(event.password))); + } + + FutureOr _onPasswordVisibilityChanged( + LoginPasswordVisibilityChanged event, + Emitter emit, + ) { + emit(state.copyWith(obscurePassword: event.obscure)); + } +} diff --git a/todo/lib/authentication/login/bloc/login_event.dart b/todo/lib/authentication/login/bloc/login_event.dart new file mode 100644 index 0000000..1ddc439 --- /dev/null +++ b/todo/lib/authentication/login/bloc/login_event.dart @@ -0,0 +1,40 @@ +part of 'login_bloc.dart'; + +@immutable +abstract class LoginEvent extends Equatable { + const LoginEvent(); +} + +class LoginEmailChanged extends LoginEvent { + const LoginEmailChanged(this.email); + + final String email; + + @override + List get props => [email]; +} + +class LoginPasswordChanged extends LoginEvent { + const LoginPasswordChanged(this.password); + + final String password; + + @override + List get props => [password]; +} + +class LoginWithEmailAndPasswordRequested extends LoginEvent { + const LoginWithEmailAndPasswordRequested(); + + @override + List get props => []; +} + +class LoginPasswordVisibilityChanged extends LoginEvent { + const LoginPasswordVisibilityChanged({required this.obscure}); + + final bool obscure; + + @override + List get props => [obscure]; +} diff --git a/todo/lib/authentication/login/bloc/login_state.dart b/todo/lib/authentication/login/bloc/login_state.dart new file mode 100644 index 0000000..2a968b4 --- /dev/null +++ b/todo/lib/authentication/login/bloc/login_state.dart @@ -0,0 +1,43 @@ +part of 'login_bloc.dart'; + +@immutable +class LoginState extends Equatable { + const LoginState({ + required this.email, + required this.password, + required this.status, + required this.obscurePassword, + }); + + const LoginState.initial() + : this( + email: const Email.pure(), + password: const Password.pure(), + status: FormzSubmissionStatus.initial, + obscurePassword: true, + ); + + final Email email; + final Password password; + final FormzSubmissionStatus status; + final bool obscurePassword; + + bool get valid => Formz.validate([email, password]); + + @override + List get props => [email, password, status, obscurePassword]; + + LoginState copyWith({ + Email? email, + Password? password, + FormzSubmissionStatus? status, + bool? obscurePassword, + }) { + return LoginState( + email: email ?? this.email, + password: password ?? this.password, + status: status ?? this.status, + obscurePassword: obscurePassword ?? this.obscurePassword, + ); + } +} diff --git a/todo/lib/authentication/login/login.dart b/todo/lib/authentication/login/login.dart new file mode 100644 index 0000000..89d8fb1 --- /dev/null +++ b/todo/lib/authentication/login/login.dart @@ -0,0 +1,2 @@ +export 'bloc/login_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/authentication/login/view/login_page.dart b/todo/lib/authentication/login/view/login_page.dart new file mode 100644 index 0000000..e6d1085 --- /dev/null +++ b/todo/lib/authentication/login/view/login_page.dart @@ -0,0 +1,23 @@ +import 'package:todo/authentication/login/login.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LoginPage extends Page { + const LoginPage({super.key}); + + static const path = '/login'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + fullscreenDialog: true, + settings: this, + builder: (context) { + return BlocProvider( + create: (_) => LoginBloc(userRepository: context.read()), + child: const LoginView(), + ); + }, + ); + } +} diff --git a/todo/lib/authentication/login/view/login_view.dart b/todo/lib/authentication/login/view/login_view.dart new file mode 100644 index 0000000..3f076e9 --- /dev/null +++ b/todo/lib/authentication/login/view/login_view.dart @@ -0,0 +1,151 @@ +import 'package:go_router/go_router.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; +import 'package:todo/authentication/login/login.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class LoginView extends StatelessWidget { + const LoginView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Scaffold( + appBar: UIAppBar(widgetTitle: Text(l10n.logIn)), + body: BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.status.isSuccess) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.logInSuccessful)), + ); + } else if (state.status.isFailure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.unknownError)), + ); + } + }, + child: ListView( + padding: const EdgeInsets.all(24), + children: const [ + _EmailTextField(), + SizedBox(height: 24), + _PasswordTextField(), + SizedBox(height: 24), + _LoginButton(), + SizedBox(height: 24), + _ForgotPasswordButton(), + SizedBox(height: 24), + _SignUpButton(), + ], + ), + ), + ); + } +} + +class _ForgotPasswordButton extends StatelessWidget { + const _ForgotPasswordButton(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return TextButton( + key: const Key('LoginForgotPasswordButton'), + onPressed: () => context.push(ForgotPasswordPage.path), + child: Text(l10n.forgotPassword), + ); + } +} + +class _SignUpButton extends StatelessWidget { + const _SignUpButton(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return TextButton( + key: const Key('LoginSignUpButton'), + onPressed: () => context.push(SignUpPage.path), + child: Text(l10n.signUp), + ); + } +} + +class _EmailTextField extends StatelessWidget { + const _EmailTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final valid = context.select((LoginBloc bloc) { + final email = bloc.state.email; + return email.isValid || email.isPure; + }); + + return UITextField.emailTextField( + key: const Key('LoginEmailTextField'), + onChanged: (value) => + context.read().add(LoginEmailChanged(value)), + hintText: l10n.email, + errorText: valid ? null : l10n.invalidEmail, + ); + } +} + +class _PasswordTextField extends StatelessWidget { + const _PasswordTextField(); + + @override + Widget build(BuildContext context) { + final obscurePassword = + context.select((LoginBloc bloc) => bloc.state.obscurePassword); + + return UITextField.passwordTextField( + key: const Key('LoginPasswordTextField'), + keyboardType: TextInputType.visiblePassword, + onChanged: (value) => + context.read().add(LoginPasswordChanged(value)), + suffix: IconButton( + icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), + onPressed: () { + context + .read() + .add(LoginPasswordVisibilityChanged(obscure: !obscurePassword)); + }, + ), + obscureText: obscurePassword, + ); + } +} + +class _LoginButton extends StatelessWidget { + const _LoginButton(); + + @override + Widget build(BuildContext context) { + final validToSubmit = context.select((LoginBloc bloc) => bloc.state.valid); + final l10n = context.l10n; + + return UIOutlinedButton.primary( + onPressed: validToSubmit + ? () { + context + .read() + .add(const LoginWithEmailAndPasswordRequested()); + } + : null, + text: l10n.logIn, + ); + } +} diff --git a/todo/lib/authentication/login/view/view.dart b/todo/lib/authentication/login/view/view.dart new file mode 100644 index 0000000..042cfbb --- /dev/null +++ b/todo/lib/authentication/login/view/view.dart @@ -0,0 +1,2 @@ +export 'login_page.dart'; +export 'login_view.dart'; diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart b/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart new file mode 100644 index 0000000..40c824b --- /dev/null +++ b/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:user_repository/user_repository.dart'; + +part 'sign_up_event.dart'; +part 'sign_up_state.dart'; + +class SignUpBloc extends Bloc { + SignUpBloc({required UserRepository userRepository}) + : _userRepository = userRepository, + super(const SignUpState.initial()) { + on(_onEmailChanged); + on(_onPasswordChanged); + on(_onConfirmationPasswordChanged); + on(_onPasswordVisibilityChanged); + on( + _onSignUpWithEmailAndPasswordRequested, + ); + on(_onNameChanged); + } + + final UserRepository _userRepository; + + FutureOr _onSignUpWithEmailAndPasswordRequested( + SignUpWithEmailAndPasswordRequested event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); + await _userRepository.signUp( + email: state.email.value, + password: state.password.value, + name: state.name.value, + ); + emit(state.copyWith(status: FormzSubmissionStatus.success)); + } on Exception { + emit(state.copyWith(status: FormzSubmissionStatus.failure)); + } + } + + FutureOr _onEmailChanged( + SignUpEmailChanged event, + Emitter emit, + ) { + emit(state.copyWith(email: Email.dirty(event.email))); + } + + FutureOr _onPasswordChanged( + SignUpPasswordChanged event, + Emitter emit, + ) { + emit(state.copyWith(password: Password.dirty(event.password))); + } + + FutureOr _onPasswordVisibilityChanged( + SignUpPasswordVisibilityChanged event, + Emitter emit, + ) { + emit(state.copyWith(obscurePasswords: event.obscure)); + } + + FutureOr _onConfirmationPasswordChanged( + SignUpConfirmationPasswordChanged event, + Emitter emit, + ) { + emit(state.copyWith(confirmationPassword: event.password)); + } + + FutureOr _onNameChanged( + SignUpNameChanged event, + Emitter emit, + ) { + emit(state.copyWith(name: Name.dirty(event.name))); + } +} diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_event.dart b/todo/lib/authentication/sign_up/bloc/sign_up_event.dart new file mode 100644 index 0000000..85603ed --- /dev/null +++ b/todo/lib/authentication/sign_up/bloc/sign_up_event.dart @@ -0,0 +1,58 @@ +part of 'sign_up_bloc.dart'; + +@immutable +abstract class SignUpEvent extends Equatable { + const SignUpEvent(); +} + +class SignUpEmailChanged extends SignUpEvent { + const SignUpEmailChanged(this.email); + + final String email; + + @override + List get props => [email]; +} + +class SignUpPasswordChanged extends SignUpEvent { + const SignUpPasswordChanged({required this.password}); + + final String password; + + @override + List get props => [password]; +} + +class SignUpConfirmationPasswordChanged extends SignUpEvent { + const SignUpConfirmationPasswordChanged({required this.password}); + + final String password; + + @override + List get props => [password]; +} + +class SignUpWithEmailAndPasswordRequested extends SignUpEvent { + const SignUpWithEmailAndPasswordRequested(); + + @override + List get props => []; +} + +class SignUpPasswordVisibilityChanged extends SignUpEvent { + const SignUpPasswordVisibilityChanged({required this.obscure}); + + final bool obscure; + + @override + List get props => [obscure]; +} + +class SignUpNameChanged extends SignUpEvent { + const SignUpNameChanged(this.name); + + final String name; + + @override + List get props => [name]; +} diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_state.dart b/todo/lib/authentication/sign_up/bloc/sign_up_state.dart new file mode 100644 index 0000000..e553dad --- /dev/null +++ b/todo/lib/authentication/sign_up/bloc/sign_up_state.dart @@ -0,0 +1,78 @@ +part of 'sign_up_bloc.dart'; + +class SignUpState extends Equatable { + const SignUpState({ + required this.status, + required this.email, + required this.password, + required this.confirmationPassword, + required this.obscurePasswords, + required this.name, + }); + + const SignUpState.initial() + : this( + status: FormzSubmissionStatus.initial, + email: const Email.pure(), + password: const Password.pure(), + name: const Name.pure(), + confirmationPassword: '', + obscurePasswords: true, + ); + + final FormzSubmissionStatus status; + final Email email; + final Password password; + final Name name; + final String confirmationPassword; + final bool obscurePasswords; + + bool get valid { + return Formz.validate([email, password, name]) && + password.value == confirmationPassword; + } + + bool get emailIsValid { + return email.isPure || email.isValid; + } + + bool get passwordIsValid { + return password.isPure || password.isValid; + } + + bool get passwordsMatch { + return password.isPure || password.value == confirmationPassword; + } + + bool get nameIsValid { + return name.isPure || name.isValid; + } + + SignUpState copyWith({ + FormzSubmissionStatus? status, + Email? email, + Password? password, + String? confirmationPassword, + bool? obscurePasswords, + Name? name, + }) { + return SignUpState( + status: status ?? this.status, + confirmationPassword: confirmationPassword ?? this.confirmationPassword, + email: email ?? this.email, + obscurePasswords: obscurePasswords ?? this.obscurePasswords, + password: password ?? this.password, + name: name ?? this.name, + ); + } + + @override + List get props => [ + status, + email, + password, + confirmationPassword, + obscurePasswords, + name, + ]; +} diff --git a/todo/lib/authentication/sign_up/sign_up.dart b/todo/lib/authentication/sign_up/sign_up.dart new file mode 100644 index 0000000..f6779d0 --- /dev/null +++ b/todo/lib/authentication/sign_up/sign_up.dart @@ -0,0 +1,2 @@ +export 'bloc/sign_up_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/authentication/sign_up/view/sign_up_page.dart b/todo/lib/authentication/sign_up/view/sign_up_page.dart new file mode 100644 index 0000000..2af9329 --- /dev/null +++ b/todo/lib/authentication/sign_up/view/sign_up_page.dart @@ -0,0 +1,22 @@ +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SignUpPage extends Page { + const SignUpPage(); + + static const path = '/sign-up'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + settings: this, + builder: (context) { + return BlocProvider( + create: (_) => SignUpBloc(userRepository: context.read()), + child: const SignUpView(), + ); + }, + ); + } +} diff --git a/todo/lib/authentication/sign_up/view/sign_up_view.dart b/todo/lib/authentication/sign_up/view/sign_up_view.dart new file mode 100644 index 0000000..ce12f0a --- /dev/null +++ b/todo/lib/authentication/sign_up/view/sign_up_view.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:go_router/go_router.dart'; +import 'package:todo/authentication/sign_up/bloc/sign_up_bloc.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class SignUpView extends StatelessWidget { + const SignUpView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.status.isSuccess) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.signUpSuccessful)), + ); + + if (context.canPop()) { + context.pop(); + } + } else if (state.status.isFailure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(l10n.unknownError)), + ); + } + }, + child: Scaffold( + appBar: AppBar(title: Text(l10n.signUp)), + body: ListView( + padding: const EdgeInsets.all(UISpacing.xlg), + children: const [ + _NameTextField(), + SizedBox(height: UISpacing.xlg), + _EmailTextField(), + SizedBox(height: UISpacing.xlg), + _PasswordTextField(), + SizedBox(height: UISpacing.xlg), + _ConfirmationPasswordTextField(), + SizedBox(height: UISpacing.xlg), + _SignUpButton(), + ], + ), + ), + ); + } +} + +class _NameTextField extends StatelessWidget { + const _NameTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final valid = context.select((SignUpBloc bloc) => bloc.state.nameIsValid); + + return TextField( + key: const Key('SignUpNameTextField'), + onChanged: (name) => + context.read().add(SignUpNameChanged(name)), + decoration: InputDecoration( + hintText: l10n.signUpName, + errorText: valid ? null : l10n.signUpNameInvalid, + ), + keyboardType: TextInputType.text, + ); + } +} + +class _EmailTextField extends StatelessWidget { + const _EmailTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final valid = context.select((SignUpBloc bloc) => bloc.state.emailIsValid); + + return TextField( + key: const Key('SignUpEmailTextField'), + onChanged: (value) => + context.read().add(SignUpEmailChanged(value)), + decoration: InputDecoration( + hintText: l10n.email, + errorText: valid ? null : l10n.invalidEmail, + ), + keyboardType: TextInputType.emailAddress, + ); + } +} + +class _PasswordTextField extends StatelessWidget { + const _PasswordTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final obscurePassword = + context.select((SignUpBloc bloc) => bloc.state.obscurePasswords); + final valid = + context.select((SignUpBloc bloc) => bloc.state.passwordIsValid); + + return TextField( + key: const Key('SignUpPasswordTextField'), + autocorrect: false, + keyboardType: TextInputType.visiblePassword, + onChanged: (password) => context + .read() + .add(SignUpPasswordChanged(password: password)), + decoration: InputDecoration( + hintText: l10n.password, + errorText: valid ? null : l10n.signUpPasswordRequirement, + suffixIcon: IconButton( + icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), + onPressed: () { + context.read().add( + SignUpPasswordVisibilityChanged(obscure: !obscurePassword), + ); + }, + ), + ), + enableIMEPersonalizedLearning: false, + obscureText: obscurePassword, + ); + } +} + +class _ConfirmationPasswordTextField extends StatelessWidget { + const _ConfirmationPasswordTextField(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final obscurePassword = + context.select((SignUpBloc bloc) => bloc.state.obscurePasswords); + final valid = + context.select((SignUpBloc bloc) => bloc.state.passwordsMatch); + + return TextField( + key: const Key('SignUpConfirmationPasswordTextField'), + autocorrect: false, + keyboardType: TextInputType.visiblePassword, + onChanged: (password) => context.read().add( + SignUpConfirmationPasswordChanged(password: password), + ), + decoration: InputDecoration( + hintText: l10n.signUpPasswordConfirmation, + errorText: valid ? null : l10n.signUpPasswordMismatch, + suffixIcon: IconButton( + icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), + onPressed: () { + context.read().add( + SignUpPasswordVisibilityChanged(obscure: !obscurePassword), + ); + }, + ), + ), + enableIMEPersonalizedLearning: false, + obscureText: obscurePassword, + ); + } +} + +class _SignUpButton extends StatelessWidget { + const _SignUpButton(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final validToSubmit = context.select((SignUpBloc bloc) => bloc.state.valid); + + return ElevatedButton( + onPressed: validToSubmit + ? () { + context + .read() + .add(const SignUpWithEmailAndPasswordRequested()); + } + : null, + child: Text(l10n.signUp), + ); + } +} diff --git a/todo/lib/authentication/sign_up/view/view.dart b/todo/lib/authentication/sign_up/view/view.dart new file mode 100644 index 0000000..31406a5 --- /dev/null +++ b/todo/lib/authentication/sign_up/view/view.dart @@ -0,0 +1,2 @@ +export 'sign_up_page.dart'; +export 'sign_up_view.dart'; diff --git a/todo/lib/down_for_maintenance/down_for_maintenance.dart b/todo/lib/down_for_maintenance/down_for_maintenance.dart new file mode 100644 index 0000000..2fd64b2 --- /dev/null +++ b/todo/lib/down_for_maintenance/down_for_maintenance.dart @@ -0,0 +1,2 @@ +export 'down_for_maintenance_page.dart'; +export 'down_for_maintenance_view.dart'; diff --git a/todo/lib/down_for_maintenance/down_for_maintenance_page.dart b/todo/lib/down_for_maintenance/down_for_maintenance_page.dart new file mode 100644 index 0000000..39170d5 --- /dev/null +++ b/todo/lib/down_for_maintenance/down_for_maintenance_page.dart @@ -0,0 +1,16 @@ +import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; +import 'package:flutter/material.dart'; + +class DownForMaintenancePage extends Page { + const DownForMaintenancePage({super.key}); + static const path = '/down-for-maintenance'; + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + settings: this, + builder: (context) { + return const DownForMaintenanceView(); + }, + ); + } +} diff --git a/todo/lib/down_for_maintenance/down_for_maintenance_view.dart b/todo/lib/down_for_maintenance/down_for_maintenance_view.dart new file mode 100644 index 0000000..958138c --- /dev/null +++ b/todo/lib/down_for_maintenance/down_for_maintenance_view.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:todo/l10n/l10n.dart'; + +class DownForMaintenanceView extends StatelessWidget { + const DownForMaintenanceView({super.key}); + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Scaffold( + body: Center( + child: Text(l10n.downForMaintenanceMessage), + ), + ); + } + + static void showAsDialog(BuildContext context) { + showAdaptiveDialog( + context: context, + builder: (context) { + final l10n = context.l10n; + return AlertDialog.adaptive( + title: Text(l10n.downForMaintenance), + content: Text( + l10n.downForMaintenanceMessage, + ), + actions: [ + TextButton( + onPressed: SystemNavigator.pop, + child: Text(l10n.ok), + ), + ], + ); + }, + ); + } +} diff --git a/todo/lib/force_upgrade/force_upgrade.dart b/todo/lib/force_upgrade/force_upgrade.dart new file mode 100644 index 0000000..6a6d060 --- /dev/null +++ b/todo/lib/force_upgrade/force_upgrade.dart @@ -0,0 +1,2 @@ +export 'force_upgrade_page.dart'; +export 'force_upgrade_view.dart'; diff --git a/todo/lib/force_upgrade/force_upgrade_page.dart b/todo/lib/force_upgrade/force_upgrade_page.dart new file mode 100644 index 0000000..18f1685 --- /dev/null +++ b/todo/lib/force_upgrade/force_upgrade_page.dart @@ -0,0 +1,16 @@ +import 'package:todo/force_upgrade/force_upgrade.dart'; +import 'package:flutter/material.dart'; + +class ForceUpgradePage extends Page { + const ForceUpgradePage({super.key}); + static const path = '/force-upgrade'; + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + settings: this, + builder: (context) { + return const ForceUpgradeView(); + }, + ); + } +} diff --git a/todo/lib/force_upgrade/force_upgrade_view.dart b/todo/lib/force_upgrade/force_upgrade_view.dart new file mode 100644 index 0000000..e49f7f3 --- /dev/null +++ b/todo/lib/force_upgrade/force_upgrade_view.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:todo/l10n/l10n.dart'; + +class ForceUpgradeView extends StatelessWidget { + const ForceUpgradeView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Scaffold( + body: Center( + child: Text( + l10n.forceUpgradeMessage, + ), + ), + ); + } + + static void showAsDialog(BuildContext context) { + showAdaptiveDialog( + context: context, + builder: (context) { + final l10n = context.l10n; + return AlertDialog.adaptive( + title: Text(l10n.updateRequired), + content: Text( + l10n.forceUpgradeMessage, + ), + actions: [ + TextButton( + onPressed: () {}, + child: Text(l10n.ok), + ), + ], + ); + }, + ); + } +} diff --git a/todo/lib/home/bloc/home_bloc.dart b/todo/lib/home/bloc/home_bloc.dart new file mode 100644 index 0000000..8794675 --- /dev/null +++ b/todo/lib/home/bloc/home_bloc.dart @@ -0,0 +1,13 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'home_event.dart'; +part 'home_state.dart'; + +class HomeBloc extends Bloc { + HomeBloc() : super(const HomeState.initial()) { + on((event, emit) { + emit(state.copyWith(tabIndex: event.tabIndex)); + }); + } +} diff --git a/todo/lib/home/bloc/home_event.dart b/todo/lib/home/bloc/home_event.dart new file mode 100644 index 0000000..95216ef --- /dev/null +++ b/todo/lib/home/bloc/home_event.dart @@ -0,0 +1,17 @@ +part of 'home_bloc.dart'; + +class HomeEvent extends Equatable { + const HomeEvent(); + + @override + List get props => []; +} + +class HomeTabChanged extends HomeEvent { + const HomeTabChanged(this.tabIndex); + + final int tabIndex; + + @override + List get props => [tabIndex]; +} diff --git a/todo/lib/home/bloc/home_state.dart b/todo/lib/home/bloc/home_state.dart new file mode 100644 index 0000000..399d220 --- /dev/null +++ b/todo/lib/home/bloc/home_state.dart @@ -0,0 +1,29 @@ +part of 'home_bloc.dart'; + +enum HomeStatus { + initial, + loading; + + bool get isLoading => this == HomeStatus.loading; +} + +class HomeState extends Equatable { + const HomeState({required this.status, this.tabIndex = 0}); + + const HomeState.initial() + : status = HomeStatus.initial, + tabIndex = 0; + + final HomeStatus status; + final int tabIndex; + + HomeState copyWith({HomeStatus? status, int? tabIndex}) { + return HomeState( + status: status ?? this.status, + tabIndex: tabIndex ?? this.tabIndex, + ); + } + + @override + List get props => [status, tabIndex]; +} diff --git a/todo/lib/home/home.dart b/todo/lib/home/home.dart new file mode 100644 index 0000000..3468ef3 --- /dev/null +++ b/todo/lib/home/home.dart @@ -0,0 +1,2 @@ +export 'bloc/home_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/home/view/home_page.dart b/todo/lib/home/view/home_page.dart new file mode 100644 index 0000000..c62dcd0 --- /dev/null +++ b/todo/lib/home/view/home_page.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/home/home.dart'; + +class HomePage extends Page { + const HomePage(); + static const path = '/home'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + fullscreenDialog: true, + settings: this, + builder: (ctx) { + return BlocProvider( + create: (_) => HomeBloc(), + child: const HomeView(), + ); + }, + ); + } +} diff --git a/todo/lib/home/view/home_view.dart b/todo/lib/home/view/home_view.dart new file mode 100644 index 0000000..91617e2 --- /dev/null +++ b/todo/lib/home/view/home_view.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/delete_account/view/delete_account_page.dart'; +import 'package:todo/home/home.dart'; +import 'package:todo/app/app.dart'; +import 'package:todo/l10n/l10n.dart'; + +class HomeView extends StatelessWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context) { + final status = context.select((HomeBloc bloc) => bloc.state.status); + final l10n = context.l10n; + + return Scaffold( + appBar: AppBar( + key: const Key('HomeAppBar'), + title: Text(l10n.home), + actions: [ + IconButton( + key: const Key('HomeDeleteAccountButton'), + icon: Text(l10n.deleteAccount), + onPressed: () { + context.push(DeleteAccountPage.path); + }, + ), + IconButton( + key: const Key('HomeLogoutButton'), + icon: Text(l10n.logOut), + onPressed: () { + showLogoutConfirmationDialog(context); + }, + ), + ], + ), + body: status.isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : const Placeholder(), + ); + } +} + +@visibleForTesting +void showLogoutConfirmationDialog(BuildContext context) { + final l10n = context.l10n; + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(l10n.logOutConfirmation), + actions: [ + TextButton( + key: const Key('CancelButton'), + child: Text(l10n.cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); +} diff --git a/todo/lib/home/view/main_screen_page_builder.dart b/todo/lib/home/view/main_screen_page_builder.dart new file mode 100644 index 0000000..71d9c11 --- /dev/null +++ b/todo/lib/home/view/main_screen_page_builder.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:todo/navigation/navigation.dart'; +import 'package:todo/home/home.dart'; +import 'package:todo/settings/settings.dart'; + +class MainScreenPageBuilder extends StatelessWidget { + const MainScreenPageBuilder({ + required this.child, + this.currentIndex, + super.key, + }); + + static NoTransitionPage route(Widget child, int? currentIndex) => + NoTransitionPage( + child: BlocProvider( + create: (context) => HomeBloc(), + child: MainScreenPageBuilder( + currentIndex: currentIndex, + child: child, + ), + ), + ); + + final Widget child; + final int? currentIndex; + + @override + Widget build(BuildContext context) { + final selectedTab = context.select((HomeBloc bloc) => bloc.state.tabIndex); + + return Scaffold( + body: child, + bottomNavigationBar: BottomNavBar( + currentIndex: selectedTab, + onTap: (index) { + context.read().add(HomeTabChanged(index)); + if (index == 0) { + context.go(HomePage.path); + } + if (index == 1) { + context.go(SettingsPage.path); + } + }, + ), + ); + } +} diff --git a/todo/lib/home/view/view.dart b/todo/lib/home/view/view.dart new file mode 100644 index 0000000..8093906 --- /dev/null +++ b/todo/lib/home/view/view.dart @@ -0,0 +1,3 @@ +export 'home_page.dart'; +export 'home_view.dart'; +export 'main_screen_page_builder.dart'; diff --git a/todo/lib/l10n/arb/app_en.arb b/todo/lib/l10n/arb/app_en.arb new file mode 100644 index 0000000..b939b2d --- /dev/null +++ b/todo/lib/l10n/arb/app_en.arb @@ -0,0 +1,131 @@ +{ + "@@locale": "en", + "home": "Home", + "@home": { + "description": "Text shown on the home page" + }, + "logInSuccessful": "Log in successful", + "@logInSuccessful": { + "description": "Text shown when the user logs in successfully" + }, + "logIn": "Log in", + "@logIn": { + "description": "Text shown on the log in button" + }, + "password": "Password", + "@password": { + "description": "Text shown on the password input" + }, + "email": "Email", + "@email": { + "description": "Text shown on the email input" + }, + "invalidEmail": "Please enter a valid email.", + "@invalidEmail": { + "description": "Text shown when the user enters an invalid email" + }, + "unknownError": "Something went wrong.", + "@unknownError": { + "description": "Text shown when an unknown error occurs" + }, + "signUpPasswordRequirement": "Password must be at least 6 characters long.", + "@signUpPasswordRequirement": { + "description": "Text shown when the user enters a password that is too short" + }, + "signUpPasswordConfirmation": "Password confirmation", + "@signUpPasswordConfirmation": { + "description": "Text shown on the password confirmation input" + }, + "signUpPasswordMismatch": "Passwords do not match.", + "@signUpPasswordMismatch": { + "description": "Text shown when the user enters two different passwords" + }, + "signUp": "Sign up", + "@signUp": { + "description": "Text shown on the sign up button" + }, + "signUpSuccessful": "Sign up successful.", + "@signUpSuccessful": { + "description": "Text shown when the user signs up successfully" + }, + "forgotPassword": "Forgot password?", + "@forgotPassword": { + "description": "Text shown on the forgot password button" + }, + "forgotPasswordDescription": "Please enter your email. We will send you a link to reset your password.", + "@forgotPasswordDescription": { + "description": "Text shown on the forgot password description" + }, + "reset": "Reset", + "@reset": { + "description": "Text shown on the reset button" + }, + "resetSuccessful": "Password reset email sent succesfully.", + "@resetSuccessful": { + "description": "Text shown when the user resets their password successfully" + }, + "signUpName": "Name", + "@signUpName": { + "description": "Text shown on the name input" + }, + "signUpNameInvalid": "Invalid name format. Please enter a valid name.", + "@signUpNameInvalid": { + "description": "Text shown when the user enters an invalid name" + }, + "logOut": "Log out", + "@logout": { + "description": "Text shown on the log out button" + }, + "logOutConfirmation": "Are you sure you want to log out?", + "@logOutConfirmation": { + "description": "Text shown on the log out confirmation" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Text shown on the cancel button" + }, + "deleteAccount": "Delete account", + "@deleteAccount": { + "description": "Text shown on the delete account button" + }, + "downForMaintenanceMessage": "The app is currently down for maintenance. Please try again later.", + "@downForMaintenanceMessage": { + "description": "Message shown when the app is down for maintenance" + }, + "downForMaintenance": "Down For Maintenance", + "@downForMaintenance": { + "description": "Message shown when the app is down for maintenance" + }, + "forceUpgradeMessage": "The app is out of date. Please update to the latest version.", + "@forceUpgradeMessage": { + "description": "Message shown when the app must be updated" + }, + "updateRequired": "Update Required", + "@updateRequired": { + "description": "Message shown when the app must be updated" + }, + "ok":"Ok", + "@ok": { + "description": "Confirm text for a button" + }, + "settings": "Settings", + "@settings": { + "description": "Text shown on the settings button" + }, + "termsAndConditions": "Terms and Conditions", + "@termsAndConditions": { + "description": "Text shown on the terms and conditions button" + }, + "aboutUs": "About Us", + "@aboutUs": { + "description": "Text shown on the about us button" + }, + "version": "Version", + "@version": { + "description": "Text shown on the version label" + }, + "contactUs": "Contact Us", + "@contactUs": { + "description": "Text shown on the contact us button" + } +} \ No newline at end of file diff --git a/todo/lib/l10n/arb/app_localizations.dart b/todo/lib/l10n/arb/app_localizations.dart new file mode 100644 index 0000000..8f7f3b7 --- /dev/null +++ b/todo/lib/l10n/arb/app_localizations.dart @@ -0,0 +1,320 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'arb/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [Locale('en')]; + + /// Text shown on the home page + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Text shown when the user logs in successfully + /// + /// In en, this message translates to: + /// **'Log in successful'** + String get logInSuccessful; + + /// Text shown on the log in button + /// + /// In en, this message translates to: + /// **'Log in'** + String get logIn; + + /// Text shown on the password input + /// + /// In en, this message translates to: + /// **'Password'** + String get password; + + /// Text shown on the email input + /// + /// In en, this message translates to: + /// **'Email'** + String get email; + + /// Text shown when the user enters an invalid email + /// + /// In en, this message translates to: + /// **'Please enter a valid email.'** + String get invalidEmail; + + /// Text shown when an unknown error occurs + /// + /// In en, this message translates to: + /// **'Something went wrong.'** + String get unknownError; + + /// Text shown when the user enters a password that is too short + /// + /// In en, this message translates to: + /// **'Password must be at least 6 characters long.'** + String get signUpPasswordRequirement; + + /// Text shown on the password confirmation input + /// + /// In en, this message translates to: + /// **'Password confirmation'** + String get signUpPasswordConfirmation; + + /// Text shown when the user enters two different passwords + /// + /// In en, this message translates to: + /// **'Passwords do not match.'** + String get signUpPasswordMismatch; + + /// Text shown on the sign up button + /// + /// In en, this message translates to: + /// **'Sign up'** + String get signUp; + + /// Text shown when the user signs up successfully + /// + /// In en, this message translates to: + /// **'Sign up successful.'** + String get signUpSuccessful; + + /// Text shown on the forgot password button + /// + /// In en, this message translates to: + /// **'Forgot password?'** + String get forgotPassword; + + /// Text shown on the forgot password description + /// + /// In en, this message translates to: + /// **'Please enter your email. We will send you a link to reset your password.'** + String get forgotPasswordDescription; + + /// Text shown on the reset button + /// + /// In en, this message translates to: + /// **'Reset'** + String get reset; + + /// Text shown when the user resets their password successfully + /// + /// In en, this message translates to: + /// **'Password reset email sent succesfully.'** + String get resetSuccessful; + + /// Text shown on the name input + /// + /// In en, this message translates to: + /// **'Name'** + String get signUpName; + + /// Text shown when the user enters an invalid name + /// + /// In en, this message translates to: + /// **'Invalid name format. Please enter a valid name.'** + String get signUpNameInvalid; + + /// No description provided for @logOut. + /// + /// In en, this message translates to: + /// **'Log out'** + String get logOut; + + /// Text shown on the log out confirmation + /// + /// In en, this message translates to: + /// **'Are you sure you want to log out?'** + String get logOutConfirmation; + + /// Text shown on the cancel button + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Text shown on the delete account button + /// + /// In en, this message translates to: + /// **'Delete account'** + String get deleteAccount; + + /// Message shown when the app is down for maintenance + /// + /// In en, this message translates to: + /// **'The app is currently down for maintenance. Please try again later.'** + String get downForMaintenanceMessage; + + /// Message shown when the app is down for maintenance + /// + /// In en, this message translates to: + /// **'Down For Maintenance'** + String get downForMaintenance; + + /// Message shown when the app must be updated + /// + /// In en, this message translates to: + /// **'The app is out of date. Please update to the latest version.'** + String get forceUpgradeMessage; + + /// Message shown when the app must be updated + /// + /// In en, this message translates to: + /// **'Update Required'** + String get updateRequired; + + /// Confirm text for a button + /// + /// In en, this message translates to: + /// **'Ok'** + String get ok; + + /// Text shown on the settings button + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Text shown on the terms and conditions button + /// + /// In en, this message translates to: + /// **'Terms and Conditions'** + String get termsAndConditions; + + /// Text shown on the about us button + /// + /// In en, this message translates to: + /// **'About Us'** + String get aboutUs; + + /// Text shown on the version label + /// + /// In en, this message translates to: + /// **'Version'** + String get version; + + /// Text shown on the contact us button + /// + /// In en, this message translates to: + /// **'Contact Us'** + String get contactUs; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/todo/lib/l10n/arb/app_localizations_en.dart b/todo/lib/l10n/arb/app_localizations_en.dart new file mode 100644 index 0000000..de9387d --- /dev/null +++ b/todo/lib/l10n/arb/app_localizations_en.dart @@ -0,0 +1,111 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get home => 'Home'; + + @override + String get logInSuccessful => 'Log in successful'; + + @override + String get logIn => 'Log in'; + + @override + String get password => 'Password'; + + @override + String get email => 'Email'; + + @override + String get invalidEmail => 'Please enter a valid email.'; + + @override + String get unknownError => 'Something went wrong.'; + + @override + String get signUpPasswordRequirement => + 'Password must be at least 6 characters long.'; + + @override + String get signUpPasswordConfirmation => 'Password confirmation'; + + @override + String get signUpPasswordMismatch => 'Passwords do not match.'; + + @override + String get signUp => 'Sign up'; + + @override + String get signUpSuccessful => 'Sign up successful.'; + + @override + String get forgotPassword => 'Forgot password?'; + + @override + String get forgotPasswordDescription => + 'Please enter your email. We will send you a link to reset your password.'; + + @override + String get reset => 'Reset'; + + @override + String get resetSuccessful => 'Password reset email sent succesfully.'; + + @override + String get signUpName => 'Name'; + + @override + String get signUpNameInvalid => + 'Invalid name format. Please enter a valid name.'; + + @override + String get logOut => 'Log out'; + + @override + String get logOutConfirmation => 'Are you sure you want to log out?'; + + @override + String get cancel => 'Cancel'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get downForMaintenanceMessage => + 'The app is currently down for maintenance. Please try again later.'; + + @override + String get downForMaintenance => 'Down For Maintenance'; + + @override + String get forceUpgradeMessage => + 'The app is out of date. Please update to the latest version.'; + + @override + String get updateRequired => 'Update Required'; + + @override + String get ok => 'Ok'; + + @override + String get settings => 'Settings'; + + @override + String get termsAndConditions => 'Terms and Conditions'; + + @override + String get aboutUs => 'About Us'; + + @override + String get version => 'Version'; + + @override + String get contactUs => 'Contact Us'; +} diff --git a/todo/lib/l10n/l10n.dart b/todo/lib/l10n/l10n.dart new file mode 100644 index 0000000..17c891b --- /dev/null +++ b/todo/lib/l10n/l10n.dart @@ -0,0 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +export 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +extension AppLocalizationsX on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this); +} diff --git a/todo/lib/main/bootstrap.dart b/todo/lib/main/bootstrap.dart new file mode 100644 index 0000000..558f731 --- /dev/null +++ b/todo/lib/main/bootstrap.dart @@ -0,0 +1,33 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/widgets.dart'; + +class AppBlocObserver extends BlocObserver { + const AppBlocObserver(); + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + log('onChange(${bloc.runtimeType}, $change)'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + log('onError(${bloc.runtimeType}, $error, $stackTrace)'); + super.onError(bloc, error, stackTrace); + } +} + +Future bootstrap(FutureOr Function() builder) async { + FlutterError.onError = (details) { + log(details.exceptionAsString(), stackTrace: details.stack); + }; + + Bloc.observer = const AppBlocObserver(); + + // Add cross-flavor configuration here + + runApp(await builder()); +} diff --git a/todo/lib/main/main.dart b/todo/lib/main/main.dart new file mode 100644 index 0000000..ab4d882 --- /dev/null +++ b/todo/lib/main/main.dart @@ -0,0 +1 @@ +export 'bootstrap.dart'; diff --git a/todo/lib/main/main_common.dart b/todo/lib/main/main_common.dart new file mode 100644 index 0000000..d5f7944 --- /dev/null +++ b/todo/lib/main/main_common.dart @@ -0,0 +1,35 @@ +import 'package:api_client/api_client.dart'; +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:auth_client/auth_client.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +Future mainCommon({ + required String apiUrl, +}) async { + final authClient = AuthenticationClient(); + final client = HttpApiClient( + apiUrl: apiUrl, + apiKey: 'apiKey', + tokenProvider: authClient, + ); + final apiClient = ApiClient(client: client); + final userRepository = UserRepository( + apiClient: apiClient, + authenticationClient: authClient, + ); + + final user = await userRepository.user.first; + + final appConfigRepository = AppConfigRepository( + buildNumber: 1, + platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, + apiClient: apiClient, + ); + + return App( + user: user, + userRepository: userRepository, + appConfigRepository: appConfigRepository, + ); +} diff --git a/todo/lib/main/main_development.dart b/todo/lib/main/main_development.dart new file mode 100644 index 0000000..0ef81c3 --- /dev/null +++ b/todo/lib/main/main_development.dart @@ -0,0 +1,14 @@ +import 'package:todo/main/main.dart'; +import 'package:todo/main/main_common.dart'; + +void main() { + bootstrap( + () { + const apiUrl = 'https://api.example.com'; + + return mainCommon( + apiUrl: apiUrl, + ); + }, + ); +} diff --git a/todo/lib/main/main_production.dart b/todo/lib/main/main_production.dart new file mode 100644 index 0000000..0ef81c3 --- /dev/null +++ b/todo/lib/main/main_production.dart @@ -0,0 +1,14 @@ +import 'package:todo/main/main.dart'; +import 'package:todo/main/main_common.dart'; + +void main() { + bootstrap( + () { + const apiUrl = 'https://api.example.com'; + + return mainCommon( + apiUrl: apiUrl, + ); + }, + ); +} diff --git a/todo/lib/main/main_staging.dart b/todo/lib/main/main_staging.dart new file mode 100644 index 0000000..0ef81c3 --- /dev/null +++ b/todo/lib/main/main_staging.dart @@ -0,0 +1,14 @@ +import 'package:todo/main/main.dart'; +import 'package:todo/main/main_common.dart'; + +void main() { + bootstrap( + () { + const apiUrl = 'https://api.example.com'; + + return mainCommon( + apiUrl: apiUrl, + ); + }, + ); +} diff --git a/todo/lib/navigation/navigation.dart b/todo/lib/navigation/navigation.dart new file mode 100644 index 0000000..00ffcf9 --- /dev/null +++ b/todo/lib/navigation/navigation.dart @@ -0,0 +1 @@ +export 'view/view.dart'; diff --git a/todo/lib/navigation/view/bottom_nav_bar.dart b/todo/lib/navigation/view/bottom_nav_bar.dart new file mode 100644 index 0000000..e1c74ab --- /dev/null +++ b/todo/lib/navigation/view/bottom_nav_bar.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:todo/l10n/l10n.dart'; + +@visibleForTesting +class BottomNavBar extends StatelessWidget { + const BottomNavBar({ + required this.currentIndex, + required this.onTap, + super.key, + }); + + final int currentIndex; + final ValueSetter onTap; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.home_outlined), + label: l10n.home, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.settings_outlined), + label: l10n.settings, + ), + ], + currentIndex: currentIndex, + onTap: onTap, + ); + } +} diff --git a/todo/lib/navigation/view/view.dart b/todo/lib/navigation/view/view.dart new file mode 100644 index 0000000..ef3bbc9 --- /dev/null +++ b/todo/lib/navigation/view/view.dart @@ -0,0 +1 @@ +export 'bottom_nav_bar.dart'; diff --git a/todo/lib/settings/bloc/settings_bloc.dart b/todo/lib/settings/bloc/settings_bloc.dart new file mode 100644 index 0000000..9073d5e --- /dev/null +++ b/todo/lib/settings/bloc/settings_bloc.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +part 'settings_event.dart'; +part 'settings_state.dart'; + +class SettingsBloc extends Bloc { + SettingsBloc() : super(const SettingsState.initial()) { + on(_onVersionNumberRetrieved); + on(_onAboutUsRetrieved); + on(_onTermsAndConditionsRetrieved); + } + + Future _onVersionNumberRetrieved( + VersionNumberRetrieved event, + Emitter emit, + ) async { + try { + final platformInfo = await PackageInfo.fromPlatform(); + + emit( + state.copyWith( + appVersion: '${platformInfo.version}+${platformInfo.buildNumber}', + ), + ); + } on Exception { + emit(state.copyWith(status: SettingsStatus.error)); + } + } + + Future _onTermsAndConditionsRetrieved( + TermsAndConditionsRetrieved event, + Emitter emit, + ) async { + try { + final termsAndConditionsUrl = Uri( + scheme: 'https', + host: 'pub.dev', + ); + final canLaunch = await canLaunchUrl(termsAndConditionsUrl); + if (canLaunch) { + await launchUrl(termsAndConditionsUrl); + } + } catch (e) { + emit(state.copyWith(status: SettingsStatus.error)); + } + } + + Future _onAboutUsRetrieved( + AboutUsRetrieved event, + Emitter emit, + ) async { + try { + final aboutUsUrl = Uri( + scheme: 'https', + host: 'pub.dev', + ); + final canLaunch = await canLaunchUrl(aboutUsUrl); + if (canLaunch) { + await launchUrl(aboutUsUrl); + } + } catch (e) { + emit(state.copyWith(status: SettingsStatus.error)); + } + } +} diff --git a/todo/lib/settings/bloc/settings_event.dart b/todo/lib/settings/bloc/settings_event.dart new file mode 100644 index 0000000..7fde87e --- /dev/null +++ b/todo/lib/settings/bloc/settings_event.dart @@ -0,0 +1,20 @@ +part of 'settings_bloc.dart'; + +abstract class SettingsEvent extends Equatable { + const SettingsEvent(); + + @override + List get props => []; +} + +class VersionNumberRetrieved extends SettingsEvent { + const VersionNumberRetrieved(); +} + +class TermsAndConditionsRetrieved extends SettingsEvent { + const TermsAndConditionsRetrieved(); +} + +class AboutUsRetrieved extends SettingsEvent { + const AboutUsRetrieved(); +} diff --git a/todo/lib/settings/bloc/settings_state.dart b/todo/lib/settings/bloc/settings_state.dart new file mode 100644 index 0000000..9731798 --- /dev/null +++ b/todo/lib/settings/bloc/settings_state.dart @@ -0,0 +1,40 @@ +part of 'settings_bloc.dart'; + +enum SettingsStatus { + initial, + loading, + loaded, + error, +} + +class SettingsState extends Equatable { + const SettingsState({ + required this.status, + required this.appVersion, + }); + + const SettingsState.initial() + : this( + status: SettingsStatus.initial, + appVersion: '', + ); + + final SettingsStatus status; + final String appVersion; + + SettingsState copyWith({ + SettingsStatus? status, + String? appVersion, + }) { + return SettingsState( + status: status ?? this.status, + appVersion: appVersion ?? this.appVersion, + ); + } + + @override + List get props => [ + status, + appVersion, + ]; +} diff --git a/todo/lib/settings/settings.dart b/todo/lib/settings/settings.dart new file mode 100644 index 0000000..cdd5a84 --- /dev/null +++ b/todo/lib/settings/settings.dart @@ -0,0 +1,2 @@ +export 'view/view.dart'; +export 'bloc/settings_bloc.dart'; diff --git a/todo/lib/settings/view/settings_page.dart b/todo/lib/settings/view/settings_page.dart new file mode 100644 index 0000000..168f857 --- /dev/null +++ b/todo/lib/settings/view/settings_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/settings/settings.dart'; + +class SettingsPage extends Page { + const SettingsPage(); + static const path = '/settings'; + + @override + Route createRoute(BuildContext context) { + return MaterialPageRoute( + fullscreenDialog: true, + settings: this, + builder: (ctx) { + return BlocProvider( + create: (_) => SettingsBloc() + ..add( + const VersionNumberRetrieved(), + ), + child: const SettingsView(), + ); + }, + ); + } +} diff --git a/todo/lib/settings/view/settings_view.dart b/todo/lib/settings/view/settings_view.dart new file mode 100644 index 0000000..70eb18f --- /dev/null +++ b/todo/lib/settings/view/settings_view.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:todo/app/app.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:todo/settings/settings.dart'; +import 'package:todo_ui/todo_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +class SettingsView extends StatelessWidget { + const SettingsView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return BlocListener( + listener: (context, state) { + if (state.status == SettingsStatus.error) { + // Handle Error + Navigator.of(context).pop(); + } + }, + child: Scaffold( + appBar: AppBar( + key: const Key('SettingsAppBar'), + title: Text(l10n.settings), + ), + body: const _SettingsForm(), + ), + ); + } +} + +class _SettingsForm extends StatelessWidget { + const _SettingsForm(); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final status = context.select((SettingsBloc bloc) => bloc.state.status); + final appVersion = + context.select((SettingsBloc bloc) => bloc.state.appVersion); + + if (status == SettingsStatus.loading) { + return const CircularProgressIndicator(); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _SettingsItem( + key: const Key('ContactUsSettingsItem'), + title: l10n.contactUs, + onTap: () {}, + ), + _SettingsItem( + key: const Key('TermsAndConditionsSettingsItem'), + title: l10n.termsAndConditions, + onTap: () {}, + ), + _SettingsItem( + key: const Key('AboutUsSettingsItem'), + title: l10n.aboutUs, + onTap: () {}, + ), + _SettingsItem( + key: const Key('LogoutSettingsItem'), + title: l10n.logOut, + onTap: () { + context.read().add(const AppLogoutRequested()); + Navigator.of(context).pop(); + }, + ), + _SettingsItem( + key: const Key('DeleteAccountSettingsItem'), + title: l10n.deleteAccount, + onTap: () { + context.push(DeleteAccountPage.path); + }, + ), + Padding( + key: const Key('AppVersionSettingsItem'), + padding: const EdgeInsets.all(UISpacing.xlg), + child: Text('${l10n.version} $appVersion'), + ), + ], + ); + } +} + +class _SettingsItem extends StatelessWidget { + const _SettingsItem({ + required this.title, + required this.onTap, + super.key, + }); + + final String title; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: UISpacing.xxxlg, + child: ListTile( + title: Text(title), + onTap: onTap, + ), + ); + } +} diff --git a/todo/lib/settings/view/view.dart b/todo/lib/settings/view/view.dart new file mode 100644 index 0000000..0caee92 --- /dev/null +++ b/todo/lib/settings/view/view.dart @@ -0,0 +1,2 @@ +export 'settings_page.dart'; +export 'settings_view.dart'; diff --git a/todo/packages/api_client/.gitignore b/todo/packages/api_client/.gitignore new file mode 100644 index 0000000..526da15 --- /dev/null +++ b/todo/packages/api_client/.gitignore @@ -0,0 +1,7 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/todo/packages/api_client/analysis_options.yaml b/todo/packages/api_client/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/api_client/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/api_client/lib/api_client.dart b/todo/packages/api_client/lib/api_client.dart new file mode 100644 index 0000000..e2658ac --- /dev/null +++ b/todo/packages/api_client/lib/api_client.dart @@ -0,0 +1,7 @@ +/// A client to communicate with todo backend +library api_client; + +export 'src/api_client.dart'; +export 'src/http_api_client.dart'; +export 'src/resources/resources.dart'; +export 'src/utils/utils.dart'; diff --git a/todo/packages/api_client/lib/src/api_client.dart b/todo/packages/api_client/lib/src/api_client.dart new file mode 100644 index 0000000..9ba8782 --- /dev/null +++ b/todo/packages/api_client/lib/src/api_client.dart @@ -0,0 +1,27 @@ +import 'package:api_client/api_client.dart'; + +/// {@template api_client} +/// A client to communicate with todo backend +/// {@endtemplate} +class ApiClient { + /// {@macro api_client} + ApiClient({ + required HttpApiClient client, + }) : _client = client; + + final HttpApiClient _client; + + /// {@macro user_resource} + UserResource get userResource { + return UserResource( + client: _client, + ); + } + + /// {@macro app_config_resource} + AppConfigResource get appConfigResource { + return AppConfigResource( + client: _client, + ); + } +} diff --git a/todo/packages/api_client/lib/src/http_api_client.dart b/todo/packages/api_client/lib/src/http_api_client.dart new file mode 100644 index 0000000..b6b6436 --- /dev/null +++ b/todo/packages/api_client/lib/src/http_api_client.dart @@ -0,0 +1,232 @@ +import 'dart:io'; + +import 'package:equatable/equatable.dart'; +import 'package:http/http.dart'; +import 'package:token_provider/token_provider.dart'; + +/// {@template api_request_options} +/// Provide optional fields to be passed for [HttpApiClient] requests. +/// {@endtemplate} +class ApiRequestOptions extends Equatable { + /// {@macro api_request_options} + const ApiRequestOptions({this.headers}); + + /// Json content type header. + static const json = ApiRequestOptions( + headers: {'Content-Type': 'application/json'}, + ); + + /// Map containing headers to be used on the request (e.g. Authorization). + final Map? headers; + + @override + List get props => [headers]; + + /// Merges this with the received [options] returning a new instance + ApiRequestOptions merge(ApiRequestOptions options) { + final newHeaders = options.headers != null || headers != null + ? { + ...headers ?? {}, + ...options.headers ?? {}, + } + : null; + + return ApiRequestOptions(headers: newHeaders); + } +} + +/// {@template http_api_client} +/// A client that prepares an http client based on an api key. +/// {@endtemplate} +class HttpApiClient { + /// {@macro http_api_client} + HttpApiClient({ + required String apiUrl, + required String apiKey, + required TokenProvider tokenProvider, + Client? httpClient, + }) : assert(!apiUrl.endsWith(_separator), () { + throw Exception('Error: api URL must not end with $_separator.'); + }), + _apiUrl = apiUrl, + _apiKey = apiKey, + _tokenProvider = tokenProvider, + _httpClient = httpClient ?? Client(); + + static const _separator = '/'; + + /// The base url of the API + final String _apiUrl; + + /// The API key used to access the http client + // ignore: unused_field + final String _apiKey; + + /// The provider of tokens for authenticated requests + final TokenProvider _tokenProvider; + + final Client _httpClient; + + ApiRequestOptions get _apiKeyHeader { + return const ApiRequestOptions( + headers: { + 'Accept': '*/*', + }, + ); + } + + Uri _buildUri(String path) { + assert(path.startsWith('/'), () { + throw Exception('Error: path must start with $_separator.'); + }); + return Uri.parse('$_apiUrl$path'); + } + + Map? _mapRequestOptions(ApiRequestOptions? options) { + if (options != null) { + return options.merge(_apiKeyHeader).headers; + } + return _apiKeyHeader.headers; + } + + Future _applyBearer(ApiRequestOptions? options) async { + final bearerToken = await _tokenProvider.fetchToken(); + if (bearerToken == null) { + throw const HttpException( + 'Attempt to make an authenticated request without a proper session', + ); + } + return ApiRequestOptions( + headers: { + 'Authorization': 'Bearer $bearerToken', + }, + ).merge(options ?? const ApiRequestOptions()); + } + + /// Similar to [post], but with applied authorization headers from the + /// session. + Future authenticatedPost( + String path, { + Object? body, + ApiRequestOptions? options, + }) async { + final authenticatedOptions = await _applyBearer(options); + return post( + path, + body: body, + options: authenticatedOptions, + ); + } + + /// Makes a post request to the given [path] and returns a future [Response]. + Future post( + String path, { + Object? body, + ApiRequestOptions? options, + }) { + return _httpClient.post( + _buildUri(path), + body: body, + headers: _mapRequestOptions(options), + ); + } + + /// Similar to [get], but with applied authorization headers from the session + Future authenticatedGet( + String path, { + ApiRequestOptions? options, + }) async { + final authenticatedOptions = await _applyBearer(options); + return get(path, options: authenticatedOptions); + } + + /// Makes a get request to the given [path] and returns a future [Response]. + Future get(String path, {ApiRequestOptions? options}) { + return _httpClient.get( + _buildUri(path), + headers: _mapRequestOptions(options), + ); + } + + /// Similar to [delete], but with applied authorization headers + /// from the session. + Future authenticatedDelete( + String path, { + Object? body, + ApiRequestOptions? options, + }) async { + final authenticatedOptions = await _applyBearer(options); + return delete(path, body: body, options: authenticatedOptions); + } + + /// Makes a delete request to the given [path] and returns a + /// future [Response]. + Future delete( + String path, { + Object? body, + ApiRequestOptions? options, + }) { + return _httpClient.delete( + _buildUri(path), + body: body, + headers: _mapRequestOptions(options), + ); + } + + /// Similar to [put], but with applied authorization headers from the session. + Future authenticatedPut( + String path, { + Object? body, + ApiRequestOptions? options, + }) async { + final authenticatedOptions = await _applyBearer(options); + return put( + path, + body: body, + options: authenticatedOptions, + ); + } + + /// Makes a put request to the given [path] and returns + /// a future [Response] + Future put( + String path, { + Object? body, + ApiRequestOptions? options, + }) { + return _httpClient.put( + _buildUri(path), + body: body, + headers: _mapRequestOptions(options), + ); + } + + /// Similar to [patch], but with applied authorization headers from the + /// session. + Future authenticatedPatch( + String path, { + Object? body, + ApiRequestOptions? options, + }) async { + final authenticatedOptions = await _applyBearer(options); + return patch( + path, + body: body, + options: authenticatedOptions, + ); + } + + /// Makes a patch request to the given [path] and returns + /// a future [Response] + Future patch( + String path, { + Object? body, + ApiRequestOptions? options, + }) { + return _httpClient.patch( + _buildUri(path), + body: body, + headers: _mapRequestOptions(options), + ); + } +} diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config.dart new file mode 100644 index 0000000..99ae036 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/app_config.dart @@ -0,0 +1,3 @@ +export 'app_config_resource.dart'; +export 'exceptions/exceptions.dart'; +export 'models/models.dart'; diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart new file mode 100644 index 0000000..5d3fc34 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:api_client/api_client.dart'; + +/// {@template app_config_resource} +/// A client for the app config resource. +/// {@endtemplate} +class AppConfigResource { + /// {@macro app_config_resource} + AppConfigResource({required HttpApiClient client}) : _client = client; + + final HttpApiClient _client; + + /// The base path for the app config resource. + static const String kUpgrade = 'app_config/upgrade'; + + /// The base path for the app config resource. + static const String kDownForMaintenance = 'app_config/down_for_maintenance'; + + /// Checks if the application is down for maintenance + Future getDownForMaintenance() async { + try { + final response = await _client.get(kDownForMaintenance); + if (response.statusCode == 200) { + final downForMaintenanceString = jsonDecode(response.body); + return downForMaintenanceString as bool; + } else { + throw CheckDownForMaintenanceFailure( + Exception( + 'Failed to check down for maintenance: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw CheckDownForMaintenanceFailure(error, stackTrace); + } + } + + /// Checks if force upgrade is required + Future getUpgrade() async { + try { + final response = await _client.get(kUpgrade); + if (response.statusCode == 200) { + final upgradeString = jsonDecode(response.body); + return Upgrade.fromJson( + Map.from(upgradeString as Map)); + } else { + throw CheckForceUpgradeFailure( + Exception( + 'Failed to check force upgrade: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw CheckForceUpgradeFailure(error, stackTrace); + } + } +} diff --git a/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart b/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart new file mode 100644 index 0000000..7ce3207 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart @@ -0,0 +1,31 @@ +import 'package:api_client/api_client.dart'; + +/// {@template app_config_exception} +/// Base class for all [AppConfigResource] exceptions. +/// {@endtemplate} +abstract class AppConfigException implements Exception { + /// {@macro app_config_exception} + const AppConfigException(this.error, this.stackTrace); + + /// The error which was caught. + final Object error; + + /// The error stack trace. + final StackTrace stackTrace; +} + +/// {@template check_force_upgrade_failure} +/// Thrown during the check force upgrade process if a failure occurs. +/// {@endtemplate} +class CheckForceUpgradeFailure extends UserException { + /// {@macro check_force_upgrade_failure} + const CheckForceUpgradeFailure(super.error, super.stackTrace); +} + +/// {@template check_down_for_maintenance_failure} +/// Thrown during the check down for maintenance process if a failure occurs. +/// {@endtemplate} +class CheckDownForMaintenanceFailure extends UserException { + /// {@macro check_down_for_maintenance_failure} + const CheckDownForMaintenanceFailure(super.error, super.stackTrace); +} diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/models.dart b/todo/packages/api_client/lib/src/resources/app_config/models/models.dart new file mode 100644 index 0000000..4d14450 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/models/models.dart @@ -0,0 +1 @@ +export 'upgrade.dart'; diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart new file mode 100644 index 0000000..aba46b6 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart @@ -0,0 +1,46 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'upgrade.g.dart'; + +/// {@template upgrade} +/// Model used to encapsulate configuration information +/// regarding a upgrade. +/// {@endtemplate} +@JsonSerializable() +class Upgrade extends Equatable { + /// {@macro force_upgrade} + const Upgrade({ + required this.androidUpgradeUrl, + required this.iosUpgradeUrl, + required this.androidBuildNumber, + required this.iosBuildNumber, + }); + + /// Deserializes the given [Map] into a [Upgrade]. + factory Upgrade.fromJson(Map json) => + _$UpgradeFromJson(json); + + /// The url where users can upgrade the android application. + final String androidUpgradeUrl; + + /// The url where users can upgrade the ios application. + final String iosUpgradeUrl; + + /// The latest android build number. + final int androidBuildNumber; + + /// The latest ios build number. + final int iosBuildNumber; + + /// Serializes the [Upgrade] into a [Map]. + Map toJson() => _$UpgradeToJson(this); + + @override + List get props => [ + androidUpgradeUrl, + iosUpgradeUrl, + androidBuildNumber, + iosBuildNumber, + ]; +} diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart new file mode 100644 index 0000000..f7fb2f8 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'upgrade.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Upgrade _$UpgradeFromJson(Map json) => Upgrade( + androidUpgradeUrl: json['androidUpgradeUrl'] as String, + iosUpgradeUrl: json['iosUpgradeUrl'] as String, + androidBuildNumber: (json['androidBuildNumber'] as num).toInt(), + iosBuildNumber: (json['iosBuildNumber'] as num).toInt(), + ); + +Map _$UpgradeToJson(Upgrade instance) => { + 'androidUpgradeUrl': instance.androidUpgradeUrl, + 'iosUpgradeUrl': instance.iosUpgradeUrl, + 'androidBuildNumber': instance.androidBuildNumber, + 'iosBuildNumber': instance.iosBuildNumber, + }; diff --git a/todo/packages/api_client/lib/src/resources/resources.dart b/todo/packages/api_client/lib/src/resources/resources.dart new file mode 100644 index 0000000..36432ee --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/resources.dart @@ -0,0 +1,2 @@ +export 'app_config/app_config.dart'; +export 'user/user.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart b/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart new file mode 100644 index 0000000..19d1b55 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart @@ -0,0 +1,71 @@ +import 'package:api_client/api_client.dart'; + +/// {@template user_exception} +/// Base class for all [UserResource] exceptions. +/// {@endtemplate} +abstract class UserException implements Exception { + /// {@macro user_exception} + const UserException(this.error, this.stackTrace); + + /// The error which was caught. + final Object error; + + /// The error stack trace. + final StackTrace stackTrace; +} + +/// {@template sign_up_failure} +/// Thrown during the sign up and create user process if a failure occurs. +/// {@endtemplate} +class SignUpFailure extends UserException { + /// {@macro sign_up_failure} + const SignUpFailure(super.error, super.stackTrace); +} + +/// {@template email_already_exist_failure} +/// Thrown during the sign up and create user process if a failure occurs. +/// {@endtemplate} +class EmailAlreadyExistFailure extends UserException { + /// {@macro email_already_exist_failure} + const EmailAlreadyExistFailure(super.error, super.stackTrace); +} + +/// {@template email_verification_failure} +/// Thrown during the email verification process if a failure occurs. +/// {@endtemplate} +class EmailVerificationFailure extends UserException { + /// {@macro email_verification_failure} + const EmailVerificationFailure(super.error, super.stackTrace); +} + +/// {@template forgot_password_failure} +/// Thrown during the forgot password process if a failure occurs. +/// {@endtemplate} +class ForgotPasswordFailure extends UserException { + /// {@macro forgot_password_failure} + const ForgotPasswordFailure(super.error, super.stackTrace); +} + +/// {@template update_password_failure} +/// Thrown during the update password process if a failure occurs. +/// {@endtemplate} +class UpdatePasswordFailure extends UserException { + /// {@macro update_password_failure} + const UpdatePasswordFailure(super.error, super.stackTrace); +} + +/// {@template send_otp_code_failure} +/// Thrown during the send otp code process if a failure occurs. +/// {@endtemplate} +class SendOtpCodeFailure extends UserException { + /// {@macro send_otp_code_failure} + const SendOtpCodeFailure(super.error, super.stackTrace); +} + +/// {@template get_authenticated_user_failure} +/// Thrown during the get authenticated user process if a failure occurs. +/// {@endtemplate} +class GetAuthenticatedUserFailure extends UserException { + /// {@macro get_authenticated_user_failure} + const GetAuthenticatedUserFailure(super.error, super.stackTrace); +} diff --git a/todo/packages/api_client/lib/src/resources/user/models/models.dart b/todo/packages/api_client/lib/src/resources/user/models/models.dart new file mode 100644 index 0000000..157c17b --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/models.dart @@ -0,0 +1,4 @@ +export 'sign_up_request.dart'; +export 'update_password_request.dart'; +export 'user_data.dart'; +export 'verification_request.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart new file mode 100644 index 0000000..a0c7ff4 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'sign_up_request.g.dart'; + +/// {@template sign_up_request} +/// The request object for signing up a new user. +/// {@endtemplate} +@JsonSerializable() +class SignUpRequest extends Equatable { + /// {@macro sign_up_request} + const SignUpRequest({ + required this.email, + required this.password, + required this.name, + }); + + /// {@macro user} + factory SignUpRequest.fromJson(Map json) { + return _$SignUpRequestFromJson(json); + } + + /// The user's email. + final String email; + + /// The user's password. + final String password; + + /// The user's name. + final String name; + + @override + List get props => [email, password, name]; + + /// Converts the request to a JSON object. + Map toJson() { + return _$SignUpRequestToJson(this); + } +} diff --git a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart new file mode 100644 index 0000000..e3dfbae --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sign_up_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SignUpRequest _$SignUpRequestFromJson(Map json) => + SignUpRequest( + email: json['email'] as String, + password: json['password'] as String, + name: json['name'] as String, + ); + +Map _$SignUpRequestToJson(SignUpRequest instance) => + { + 'email': instance.email, + 'password': instance.password, + 'name': instance.name, + }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart new file mode 100644 index 0000000..3ecc179 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'update_password_request.g.dart'; + +/// {@template update_password_request} +/// The request object for updating a user's password. +/// {@endtemplate} +@JsonSerializable() +class UpdatePasswordRequest extends Equatable { + /// {@macro update_password_request} + const UpdatePasswordRequest({ + required this.email, + required this.password, + required this.confirmationCode, + }); + + /// {@macro user} + factory UpdatePasswordRequest.fromJson(Map json) { + return _$UpdatePasswordRequestFromJson(json); + } + + /// The user's email. + final String email; + + /// The user's password. + final String password; + + /// The confirmation code. + final String confirmationCode; + + @override + List get props => [email, password, confirmationCode]; + + /// Converts the request to a JSON object. + Map toJson() { + return _$UpdatePasswordRequestToJson(this); + } +} diff --git a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart new file mode 100644 index 0000000..70c38f0 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'update_password_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UpdatePasswordRequest _$UpdatePasswordRequestFromJson( + Map json) => + UpdatePasswordRequest( + email: json['email'] as String, + password: json['password'] as String, + confirmationCode: json['confirmationCode'] as String, + ); + +Map _$UpdatePasswordRequestToJson( + UpdatePasswordRequest instance) => + { + 'email': instance.email, + 'password': instance.password, + 'confirmationCode': instance.confirmationCode, + }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/user_data.dart b/todo/packages/api_client/lib/src/resources/user/models/user_data.dart new file mode 100644 index 0000000..c2ab789 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/user_data.dart @@ -0,0 +1,43 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'user_data.g.dart'; + +/// {@template user_data} +/// A model representing a todo user. +/// {@endtemplate} +@JsonSerializable() +class UserData extends Equatable { + /// {@macro user_data} + const UserData({ + required this.id, + required this.email, + required this.name, + }); + + /// {@macro user_data} + factory UserData.fromJson(Map json) { + return _$UserDataFromJson(json); + } + + /// The user unique identifer. + final String id; + + /// The user email address. + final String email; + + /// The user name. + final String name; + + @override + List get props => [ + id, + email, + name, + ]; + + /// Returns a json built from this user. + Map toJson() { + return _$UserDataToJson(this); + } +} diff --git a/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart b/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart new file mode 100644 index 0000000..0d998c5 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserData _$UserDataFromJson(Map json) => UserData( + id: json['id'] as String, + email: json['email'] as String, + name: json['name'] as String, + ); + +Map _$UserDataToJson(UserData instance) => { + 'id': instance.id, + 'email': instance.email, + 'name': instance.name, + }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart b/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart new file mode 100644 index 0000000..4b6e568 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'verification_request.g.dart'; + +/// {@template email_verification_request} +/// The request object for email verification. +/// {@endtemplate} +@JsonSerializable() +class EmailVerificationRequest extends Equatable { + /// {@macro email_verification_request} + const EmailVerificationRequest({ + required this.email, + required this.code, + }); + + /// {@macro user} + factory EmailVerificationRequest.fromJson(Map json) { + return _$EmailVerificationRequestFromJson(json); + } + + /// The user's email. + final String email; + + /// The otp code received by email. + final String code; + + @override + List get props => [email, code]; + + /// Converts the request to a JSON object. + Map toJson() { + return _$EmailVerificationRequestToJson(this); + } +} diff --git a/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart new file mode 100644 index 0000000..b58341c --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'verification_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EmailVerificationRequest _$EmailVerificationRequestFromJson( + Map json) => + EmailVerificationRequest( + email: json['email'] as String, + code: json['code'] as String, + ); + +Map _$EmailVerificationRequestToJson( + EmailVerificationRequest instance) => + { + 'email': instance.email, + 'code': instance.code, + }; diff --git a/todo/packages/api_client/lib/src/resources/user/user.dart b/todo/packages/api_client/lib/src/resources/user/user.dart new file mode 100644 index 0000000..97901cb --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/user.dart @@ -0,0 +1,3 @@ +export 'exceptions/exceptions.dart'; +export 'models/models.dart'; +export 'user_resource.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/user_resource.dart b/todo/packages/api_client/lib/src/resources/user/user_resource.dart new file mode 100644 index 0000000..85beb43 --- /dev/null +++ b/todo/packages/api_client/lib/src/resources/user/user_resource.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; + +import 'package:api_client/api_client.dart'; + +/// {@template user_resource} +/// A client for the user resource. +/// {@endtemplate} +class UserResource { + /// {@macro user_resource} + UserResource({required HttpApiClient client}) : _client = client; + + final HttpApiClient _client; + + /// The API endpoint for user sign-up. + static const kSignUp = '/auth/sign-up'; + + /// The API endpoint for email verification. + static const kVerifyEmail = '/auth/verification'; + + /// The API endpoint for password recovery. + static const kPasswordRecovery = '/auth/password-recovery'; + + /// The API endpoint for password confirmation. + static const kPasswordConfirmation = '/auth/password-confirmation'; + + /// The API endpoint for resending email verification. + static const kCodeVerification = '/auth/resend-verification'; + + /// The API endpoint for user-related operations. + static const kUser = '/user'; + + /// Attempts to sign up a new user with the provided [request]. + /// Throws a [SignUpFailure] if an error occurs. + Future signUp(SignUpRequest request) async { + try { + final response = await _client.post( + kSignUp, + body: request.toJson(), + ); + if (response.statusCode == 201) { + return; + } else if (response.statusCode == 409) { + throw EmailAlreadyExistFailure( + 'Failed to sign up user: ${response.statusCode} ${response.body}', + StackTrace.current, + ); + } else { + final body = jsonDecode(response.body) as Map; + final message = body['message'] as String; + throw SignUpFailure( + 'Failed to sign up user: $message [${response.statusCode}]', + StackTrace.current, + ); + } + } on Exception { + rethrow; + } + } + + /// Attempts to verify an email with the provided [request]. + /// + /// Throws a [EmailVerificationFailure] if an error occurs. + Future verifyEmail(EmailVerificationRequest request) async { + try { + final response = await _client.post( + kVerifyEmail, + body: request.toJson(), + ); + if (response.statusCode != 201) { + throw EmailVerificationFailure( + Exception( + 'Failed to verify email: ${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw EmailVerificationFailure(error, stackTrace); + } + } + + /// Attempts to recover the password for the provided [email]. + /// + /// Throws a [ForgotPasswordFailure] if an error occurs. + Future forgotPassword(String email) async { + try { + final response = await _client.post( + kPasswordRecovery, + body: {'email': email}, + ); + if (response.statusCode != 201) { + throw ForgotPasswordFailure( + Exception( + 'Failed to recover password of email: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw ForgotPasswordFailure(error, stackTrace); + } + } + + /// Attempts to update the password with the provided [request]. + /// + /// Throws a [UpdatePasswordFailure] if an error occurs. + Future updatePassword(UpdatePasswordRequest request) async { + try { + final response = await _client.post( + kPasswordConfirmation, + body: request.toJson(), + ); + if (response.statusCode != 201) { + throw UpdatePasswordFailure( + Exception( + 'Failed to update password: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw UpdatePasswordFailure(error, stackTrace); + } + } + + /// Attempts to resend verification for the provided [email]. + /// + /// Throws a [SendOtpCodeFailure] if an error occurs. + Future sendOtpCode(String email) async { + try { + final response = await _client.post( + kCodeVerification, + body: {'email': email}, + ); + if (response.statusCode != 201) { + throw SendOtpCodeFailure( + Exception( + 'Failed to resend verification: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw SendOtpCodeFailure(error, stackTrace); + } + } + + /// Attempts return the authenticated user. + /// + /// Throws a [GetAuthenticatedUserFailure] if an error occurs. + Future getAuthenticatedUser() async { + try { + final response = await _client.authenticatedGet(kUser); + if (response.statusCode == 200) { + final userString = jsonDecode(response.body); + return UserData.fromJson(Map.from(userString as Map)); + } else { + throw GetAuthenticatedUserFailure( + Exception( + 'Failed to get user: ' + '${response.statusCode} ${response.body}', + ), + StackTrace.current, + ); + } + } catch (error, stackTrace) { + throw GetAuthenticatedUserFailure(error, stackTrace); + } + } +} diff --git a/todo/packages/api_client/lib/src/utils/utils.dart b/todo/packages/api_client/lib/src/utils/utils.dart new file mode 100644 index 0000000..a8e8a6f --- /dev/null +++ b/todo/packages/api_client/lib/src/utils/utils.dart @@ -0,0 +1,8 @@ +/// Utility class for the API client. +class Utils { + /// Converts a list of strings to a single string, separated by commas. + static String? tryToListStringToString(List? value) { + if (value == null) return null; + return value.map((e) => e).join(','); + } +} diff --git a/todo/packages/api_client/pubspec.yaml b/todo/packages/api_client/pubspec.yaml new file mode 100644 index 0000000..618e84e --- /dev/null +++ b/todo/packages/api_client/pubspec.yaml @@ -0,0 +1,23 @@ +name: api_client +description: A client to communicate with todo backend +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + auth_client: + path: ../auth_client + equatable: ^2.0.5 + http: ^1.2.2 + json_annotation: ^4.9.0 + token_provider: + path: ../token_provider + +dev_dependencies: + build_runner: ^2.4.13 + json_serializable: ^6.8.0 + mocktail: ^1.0.4 + test: ^1.25.8 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/api_client/test/src/api_client_test.dart b/todo/packages/api_client/test/src/api_client_test.dart new file mode 100644 index 0000000..60e3c9e --- /dev/null +++ b/todo/packages/api_client/test/src/api_client_test.dart @@ -0,0 +1,31 @@ +import 'package:api_client/api_client.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockHttpApiClient extends Mock implements HttpApiClient {} + +void main() { + group('ApiClient', () { + late HttpApiClient httpClient; + late ApiClient apiClient; + + setUp(() { + httpClient = MockHttpApiClient(); + apiClient = ApiClient(client: httpClient); + }); + + test('can be instantiated', () { + expect(apiClient, isNotNull); + }); + + test('userResource returns a valid UserResource instance', () { + final userResource = apiClient.userResource; + expect(userResource, isA()); + }); + + test('appConfigResource returns a valid AppConfigResource instance', () { + final appConfigResource = apiClient.appConfigResource; + expect(appConfigResource, isA()); + }); + }); +} diff --git a/todo/packages/api_client/test/src/http_api_client_test.dart b/todo/packages/api_client/test/src/http_api_client_test.dart new file mode 100644 index 0000000..c3cb849 --- /dev/null +++ b/todo/packages/api_client/test/src/http_api_client_test.dart @@ -0,0 +1,498 @@ +import 'package:api_client/api_client.dart'; +import 'package:http/http.dart'; + +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:token_provider/token_provider.dart'; + +class MockClient extends Mock implements Client {} + +class MockTokenProvider extends Mock implements TokenProvider {} + +void main() { + group('HttpApiClient', () { + const path = '/some-api'; + const apiUrl = 'https://api.com'; + const apiKey = 'myApiKey'; + const apiKeyHeaders = { + 'Accept': '*/*', + }; + + late Client client; + late TokenProvider tokenProvider; + late HttpApiClient subject; + + Uri buildUri(String path) { + return Uri.parse('$apiUrl$path'); + } + + setUp(() { + tokenProvider = MockTokenProvider(); + client = MockClient(); + + subject = HttpApiClient( + apiUrl: apiUrl, + apiKey: apiKey, + httpClient: client, + tokenProvider: tokenProvider, + ); + }); + + test('can be instantiated', () { + expect( + HttpApiClient( + apiUrl: apiUrl, + apiKey: apiKey, + tokenProvider: tokenProvider, + ), + isNotNull, + ); + }); + + test('throw AssertionError when apiUrl ends with /', () { + expect( + () => HttpApiClient( + apiUrl: '$apiUrl/', + apiKey: apiKey, + tokenProvider: tokenProvider, + ), + throwsA(isA()), + ); + }); + + test('throw AssertionError when path does not start with /', () { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'Bearer token', + ); + + expectLater( + () async => subject.get('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.authenticatedGet('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.delete('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.authenticatedDelete('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.post('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.authenticatedPost('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.put('path'), + throwsA(isA()), + ); + + expectLater( + () async => subject.authenticatedPut('path'), + throwsA(isA()), + ); + }); + + group('applies the api key and base url to requests', () { + final uri = buildUri(path); + final body = {'message': 'data'}; + + test('on a get request', () async { + when(() => client.get(uri, headers: apiKeyHeaders)).thenAnswer( + (_) async => Response('body', 200), + ); + + final response = await subject.get(path); + + verify( + () => client.get(uri, headers: apiKeyHeaders), + ).called(1); + expect(response.statusCode, 200); + expect(response.body, 'body'); + }); + + test('on a delete request', () async { + when(() => client.delete(uri, headers: apiKeyHeaders)).thenAnswer( + (_) async => Response('body', 200), + ); + + final response = await subject.delete(path); + + verify( + () => client.delete(uri, headers: apiKeyHeaders), + ).called(1); + expect(response.statusCode, 200); + expect(response.body, 'body'); + }); + + test('on a post request', () async { + when( + () => client.post( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).thenAnswer( + (_) async => Response('body', 200), + ); + + await subject.post(path, body: body); + + verify( + () => client.post( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).called(1); + }); + + test('on a put request', () async { + when( + () => client.put( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).thenAnswer( + (_) async => Response('body', 200), + ); + + await subject.put(path, body: body); + + verify( + () => client.put( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).called(1); + }); + + test('on a patch request', () async { + when( + () => client.patch( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).thenAnswer( + (_) async => Response('body', 200), + ); + + await subject.patch(path, body: body); + + verify( + () => client.patch( + uri, + headers: apiKeyHeaders, + body: body, + ), + ).called(1); + }); + }); + + group('applies the options to the request', () { + final uri = buildUri(path); + final options = const ApiRequestOptions( + headers: { + 'Authorization': 'Bearer token', + }, + ).merge(const ApiRequestOptions(headers: apiKeyHeaders)); + + test('on a get request', () async { + when( + () => client.get(uri, headers: options.headers), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.get(path, options: options); + + verify(() => client.get(uri, headers: options.headers)).called(1); + }); + + test('on a delete request', () async { + when( + () => client.delete(uri, headers: options.headers), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.delete(path, options: options); + + verify(() => client.delete(uri, headers: options.headers)).called(1); + }); + + test('on a post request', () async { + when( + () => client.post( + uri, + headers: options.headers, + body: {'message': 'data'}, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.post( + path, + options: options, + body: {'message': 'data'}, + ); + + verify( + () => client.post( + uri, + headers: options.headers, + body: {'message': 'data'}, + ), + ).called(1); + }); + + test('on a put request', () async { + when( + () => client.put( + uri, + headers: options.headers, + body: {'message': 'data'}, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.put( + path, + options: options, + body: {'message': 'data'}, + ); + + verify( + () => client.put( + uri, + headers: options.headers, + body: {'message': 'data'}, + ), + ).called(1); + }); + }); + + group('authenticated requests', () { + final uri = buildUri('/some-api'); + + test('adds the bearer to get request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.get( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedGet(path); + verify( + () => client.get( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + ), + ).called(1); + }); + + test('adds the bearer to delete request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.delete( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedDelete(path); + verify( + () => client.delete( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + ), + ).called(1); + }); + + test('adds the bearer to post request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.post( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedPost(path, body: {'body'}); + verify( + () => client.post( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).called(1); + }); + + test('adds the bearer to put request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.put( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedPut(path, body: {'body'}); + verify( + () => client.put( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).called(1); + }); + + test('adds the bearer to patch request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.patch( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedPatch(path, body: {'body'}); + verify( + () => client.patch( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + }, + body: {'body'}, + ), + ).called(1); + }); + + test('correctly merges options on a get request', () async { + when(() => tokenProvider.fetchToken()).thenAnswer( + (_) async => 'MyBearerToken', + ); + when( + () => client.get( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + 'message': 'data', + }, + ), + ).thenAnswer((_) async => Response('body', 200)); + + await subject.authenticatedGet( + path, + options: const ApiRequestOptions( + headers: { + 'message': 'data', + }, + ), + ); + verify( + () => client.get( + uri, + headers: { + 'Accept': '*/*', + 'Authorization': 'Bearer MyBearerToken', + 'message': 'data', + }, + ), + ).called(1); + }); + }); + + group('ApiOptions', () { + group('merge', () { + test('creates a new instance with the merged attributes', () { + const options1 = ApiRequestOptions( + headers: { + 'Content-Type': 'json', + }, + ); + const options2 = ApiRequestOptions( + headers: { + 'Authorization': 'Bearer token', + }, + ); + + expect( + options1.merge(options2), + equals( + const ApiRequestOptions( + headers: { + 'Content-Type': 'json', + 'Authorization': 'Bearer token', + }, + ), + ), + ); + }); + test('can handle null values', () { + const options1 = ApiRequestOptions(); + const options2 = ApiRequestOptions(); + + expect( + options1.merge(options2).headers, + isNull, + ); + }); + }); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart b/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart new file mode 100644 index 0000000..baea00f --- /dev/null +++ b/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:api_client/api_client.dart'; +import 'package:http/http.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockHttpApiClient extends Mock implements HttpApiClient {} + +void main() { + late HttpApiClient mockHttpApiClient; + late AppConfigResource appConfigResource; + + setUp(() { + mockHttpApiClient = MockHttpApiClient(); + appConfigResource = AppConfigResource(client: mockHttpApiClient); + }); + + group('AppConfigResource', () { + test('getDownForMaintenance returns true when API call succeeds', () async { + final response = Response(jsonEncode(true), 200); + when(() => mockHttpApiClient.get(AppConfigResource.kDownForMaintenance)) + .thenAnswer((_) async => response); + + final result = await appConfigResource.getDownForMaintenance(); + + expect(result, isTrue); + }); + + test('getDownForMaintenance throws CheckDownForMaintenanceFailure on error', + () async { + final response = Response('Internal Server Error', 500); + when(() => mockHttpApiClient.get(AppConfigResource.kDownForMaintenance)) + .thenAnswer((_) async => response); + + expect( + () async => appConfigResource.getDownForMaintenance(), + throwsA(isA()), + ); + }); + + test('getUpgrade returns Upgrade when API call succeeds', () async { + final upgradeJson = { + 'androidBuildNumber': 22, + 'androidUpgradeUrl': 'https://example.com/upgrade', + 'iosBuildNumber': 22, + 'iosUpgradeUrl': 'https://example.com/upgrade', + }; + final response = Response(jsonEncode(upgradeJson), 200); + when(() => mockHttpApiClient.get(AppConfigResource.kUpgrade)) + .thenAnswer((_) async => response); + + final result = await appConfigResource.getUpgrade(); + + expect(result, isA()); + expect(result.androidBuildNumber, 22); + expect(result.androidUpgradeUrl, 'https://example.com/upgrade'); + expect(result.iosBuildNumber, 22); + expect(result.iosUpgradeUrl, 'https://example.com/upgrade'); + }); + + test('getUpgrade throws CheckForceUpgradeFailure on error', () async { + final response = Response('Internal Server Error', 500); + when(() => mockHttpApiClient.get(AppConfigResource.kUpgrade)) + .thenAnswer((_) async => response); + + expect( + () async => appConfigResource.getUpgrade(), + throwsA(isA()), + ); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart b/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart new file mode 100644 index 0000000..42ffa61 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart @@ -0,0 +1,43 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +class TestAppConfigException extends AppConfigException { + const TestAppConfigException(super.error, super.stackTrace); +} + +void main() { + group('AppConfigException', () { + test('can be instantiated', () { + final error = Exception('test error'); + final stackTrace = StackTrace.current; + + final exception = TestAppConfigException(error, stackTrace); + + expect(exception, isA()); + expect(exception.error, error); + expect(exception.stackTrace, stackTrace); + }); + + test('CheckForceUpgradeFailure can be instantiated', () { + final error = Exception('force upgrade error'); + final stackTrace = StackTrace.current; + + final exception = CheckForceUpgradeFailure(error, stackTrace); + + expect(exception, isA()); + expect(exception.error, error); + expect(exception.stackTrace, stackTrace); + }); + + test('CheckDownForMaintenanceFailure can be instantiated', () { + final error = Exception('down for maintenance error'); + final stackTrace = StackTrace.current; + + final exception = CheckDownForMaintenanceFailure(error, stackTrace); + + expect(exception, isA()); + expect(exception.error, error); + expect(exception.stackTrace, stackTrace); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart b/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart new file mode 100644 index 0000000..d553235 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart @@ -0,0 +1,44 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('Upgrade', () { + const upgrade = Upgrade( + androidUpgradeUrl: 'https://example.com/android', + iosUpgradeUrl: 'https://example.com/ios', + androidBuildNumber: 100, + iosBuildNumber: 200, + ); + + const upgradeJson = { + 'androidUpgradeUrl': 'https://example.com/android', + 'iosUpgradeUrl': 'https://example.com/ios', + 'androidBuildNumber': 100, + 'iosBuildNumber': 200, + }; + + test('supports value equality', () { + expect(upgrade, Upgrade.fromJson(upgradeJson)); + }); + + test('props are correct', () { + expect( + upgrade.props, + [ + 'https://example.com/android', + 'https://example.com/ios', + 100, + 200, + ], + ); + }); + + test('can be serialized to JSON', () { + expect(upgrade.toJson(), upgradeJson); + }); + + test('can be deserialized from JSON', () { + expect(Upgrade.fromJson(upgradeJson), upgrade); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart b/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart new file mode 100644 index 0000000..969fce9 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart @@ -0,0 +1,52 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('SignInFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Invalid arguments'); + final stackTrace = StackTrace.current; + + final signInFailure = SignInFailure(error, stackTrace); + + expect(signInFailure.error, equals(error)); + expect(signInFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('InvalidUserFailure', () { + test('constructs correctly', () { + final error = StateError('User not found'); + final stackTrace = StackTrace.current; + + final invalidUserFailure = InvalidUserFailure(error, stackTrace); + + expect(invalidUserFailure.error, equals(error)); + expect(invalidUserFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('ReAuthenticateFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Could not reauthenticate'); + final stackTrace = StackTrace.current; + + final reauthenticateFailure = ReAuthenticateFailure(error, stackTrace); + + expect(reauthenticateFailure.error, equals(error)); + expect(reauthenticateFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('DeleteAccountFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Invalid arguments'); + final stackTrace = StackTrace.current; + + final deleteAccountFailure = DeleteAccountFailure(error, stackTrace); + + expect(deleteAccountFailure.error, equals(error)); + expect(deleteAccountFailure.stackTrace, equals(stackTrace)); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart b/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart new file mode 100644 index 0000000..94bd7ed --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart @@ -0,0 +1,54 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('SignUpRequest', () { + const email = 'test@example.com'; + const password = 'password'; + const name = 'John Doe'; + + test('constructs properly', () { + const request = SignUpRequest( + email: email, + password: password, + name: name, + ); + expect(request.email, email); + expect(request.password, password); + expect(request.name, name); + }); + + test('fromJson creates a valid instance', () { + final json = { + 'email': email, + 'password': password, + 'name': name, + }; + final request = SignUpRequest.fromJson(json); + expect(request.email, email); + expect(request.password, password); + expect(request.name, name); + }); + + test('toJson returns a valid JSON object', () { + const request = SignUpRequest( + email: email, + password: password, + name: name, + ); + final json = request.toJson(); + expect(json['email'], email); + expect(json['password'], password); + expect(json['name'], name); + }); + + test('props list contains email, password, and name', () { + const request = SignUpRequest( + email: email, + password: password, + name: name, + ); + expect(request.props, containsAll([email, password, name])); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart b/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart new file mode 100644 index 0000000..f3b6575 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart @@ -0,0 +1,54 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('UpdatePasswordRequest', () { + const email = 'test@example.com'; + const password = 'newPassword'; + const confirmationCode = '123456'; + + test('constructs properly', () { + const request = UpdatePasswordRequest( + email: email, + password: password, + confirmationCode: confirmationCode, + ); + expect(request.email, email); + expect(request.password, password); + expect(request.confirmationCode, confirmationCode); + }); + + test('fromJson creates a valid instance', () { + final json = { + 'email': email, + 'password': password, + 'confirmationCode': confirmationCode, + }; + final request = UpdatePasswordRequest.fromJson(json); + expect(request.email, email); + expect(request.password, password); + expect(request.confirmationCode, confirmationCode); + }); + + test('toJson returns a valid JSON object', () { + const request = UpdatePasswordRequest( + email: email, + password: password, + confirmationCode: confirmationCode, + ); + final json = request.toJson(); + expect(json['email'], email); + expect(json['password'], password); + expect(json['confirmationCode'], confirmationCode); + }); + + test('props list contains email, password, and confirmationCode', () { + const request = UpdatePasswordRequest( + email: email, + password: password, + confirmationCode: confirmationCode, + ); + expect(request.props, containsAll([email, password, confirmationCode])); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart b/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart new file mode 100644 index 0000000..0a7abbf --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart @@ -0,0 +1,54 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('UserData', () { + const id = '123'; + const email = 'test@example.com'; + const name = 'John Doe'; + + test('constructs properly', () { + const userData = UserData( + id: id, + email: email, + name: name, + ); + expect(userData.id, id); + expect(userData.email, email); + expect(userData.name, name); + }); + + test('fromJson creates a valid instance', () { + final json = { + 'id': id, + 'email': email, + 'name': name, + }; + final userData = UserData.fromJson(json); + expect(userData.id, id); + expect(userData.email, email); + expect(userData.name, name); + }); + + test('toJson returns a valid JSON object', () { + const userData = UserData( + id: id, + email: email, + name: name, + ); + final json = userData.toJson(); + expect(json['id'], id); + expect(json['email'], email); + expect(json['name'], name); + }); + + test('props list contains id, email, and name', () { + const userData = UserData( + id: id, + email: email, + name: name, + ); + expect(userData.props, containsAll([id, email, name])); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart b/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart new file mode 100644 index 0000000..20fd397 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart @@ -0,0 +1,37 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('EmailVerificationRequest', () { + const email = 'test@example.com'; + const code = '123456'; + + test('constructs properly', () { + const request = EmailVerificationRequest(email: email, code: code); + expect(request.email, email); + expect(request.code, code); + }); + + test('fromJson creates a valid instance', () { + final json = { + 'email': email, + 'code': code, + }; + final request = EmailVerificationRequest.fromJson(json); + expect(request.email, email); + expect(request.code, code); + }); + + test('toJson returns a valid JSON object', () { + const request = EmailVerificationRequest(email: email, code: code); + final json = request.toJson(); + expect(json['email'], email); + expect(json['code'], code); + }); + + test('props list contains email and code', () { + const request = EmailVerificationRequest(email: email, code: code); + expect(request.props, containsAll([email, code])); + }); + }); +} diff --git a/todo/packages/api_client/test/src/resources/user/user_resources_test.dart b/todo/packages/api_client/test/src/resources/user/user_resources_test.dart new file mode 100644 index 0000000..5b9ec76 --- /dev/null +++ b/todo/packages/api_client/test/src/resources/user/user_resources_test.dart @@ -0,0 +1,458 @@ +import 'dart:convert'; + +import 'package:api_client/api_client.dart'; +import 'package:http/http.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockHttpApiClient extends Mock implements HttpApiClient {} + +class MockUserResource extends Mock implements UserResource {} + +void main() { + group('UserResource', () { + late HttpApiClient client; + late UserResource subject; + + setUp(() { + client = MockHttpApiClient(); + subject = UserResource(client: client); + }); + + test('signs up user correctly', () async { + when( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 201))); + + await subject.signUp( + const SignUpRequest( + email: 'test@example.com', + password: 'password', + name: 'Test User', + ), + ); + + verify( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).called(1); + }); + + test('throws EmailAlreadyExistFailure on duplicate email during sign-up', + () async { + when( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 409))); + + expect( + () => subject.signUp( + const SignUpRequest( + email: 'test@example.com', + password: 'password', + name: 'Test User', + ), + ), + throwsA(isA()), + ); + + verify( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).called(1); + }); + + test('throws error on status != 201 and != 409 during sign-up', () async { + when( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).thenAnswer( + (_) async => Future.value( + Response('{"message": "This is a test message"}', 500), + ), + ); + + expect( + () => subject.signUp( + const SignUpRequest( + email: 'test@example.com', + password: 'password', + name: 'Test User', + ), + ), + throwsA(isA()), + ); + + verify( + () => client.post( + UserResource.kSignUp, + body: { + 'email': 'test@example.com', + 'password': 'password', + 'name': 'Test User', + }, + ), + ).called(1); + }); + + test('Verifies email successfully', () async { + when( + () => client.post( + UserResource.kVerifyEmail, + body: { + 'email': 'email', + 'code': 'code', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 201))); + + await subject.verifyEmail( + const EmailVerificationRequest( + email: 'email', + code: 'code', + ), + ); + + verify( + () => client.post( + UserResource.kVerifyEmail, + body: { + 'email': 'email', + 'code': 'code', + }, + ), + ).called(1); + }); + + test('Verifies email failure with response code != 201', () async { + when( + () => client.post( + UserResource.kVerifyEmail, + body: { + 'email': 'email', + 'code': 'code', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 200))); + + expect( + () => subject.verifyEmail( + const EmailVerificationRequest( + email: 'email', + code: 'code', + ), + ), + throwsA(isA()), + ); + }); + + test('Verifies email failure', () async { + when( + () => client.post( + UserResource.kVerifyEmail, + body: { + 'email': 'email', + 'code': 'code', + }, + ), + ).thenThrow(Exception()); + + expect( + () => subject.verifyEmail( + const EmailVerificationRequest( + email: 'email', + code: 'code', + ), + ), + throwsA(isA()), + ); + }); + + test('Recovers password successfully', () async { + when( + () => client.post( + UserResource.kPasswordRecovery, + body: { + 'email': 'email', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 201))); + + await subject.forgotPassword( + 'email', + ); + + verify( + () => client.post( + UserResource.kPasswordRecovery, + body: { + 'email': 'email', + }, + ), + ).called(1); + }); + + test('Recovers password failure with response code != 201', () async { + when( + () => client.post( + UserResource.kPasswordRecovery, + body: { + 'email': 'email', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 200))); + + expect( + () => subject.forgotPassword( + 'email', + ), + throwsA(isA()), + ); + }); + + test('Recovers password with error', () async { + when( + () => client.post( + UserResource.kPasswordRecovery, + body: { + 'email': 'email', + }, + ), + ).thenThrow(Exception()); + + expect( + () => subject.forgotPassword( + 'email', + ), + throwsA(isA()), + ); + }); + + test('Updates password successfully', () async { + when( + () => client.post( + UserResource.kPasswordConfirmation, + body: { + 'email': 'email', + 'password': 'password', + 'confirmationCode': 'confirmationCode', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 201))); + + await subject.updatePassword( + const UpdatePasswordRequest( + email: 'email', + password: 'password', + confirmationCode: 'confirmationCode', + ), + ); + + verify( + () => client.post( + UserResource.kPasswordConfirmation, + body: { + 'email': 'email', + 'password': 'password', + 'confirmationCode': 'confirmationCode', + }, + ), + ).called(1); + }); + + test('Updates password failure with response code != 201', () async { + when( + () => client.post( + UserResource.kPasswordConfirmation, + body: { + 'email': 'email', + 'password': 'password', + 'confirmationCode': 'confirmationCode', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 200))); + + expect( + () => subject.updatePassword( + const UpdatePasswordRequest( + email: 'email', + password: 'password', + confirmationCode: 'confirmationCode', + ), + ), + throwsA(isA()), + ); + }); + + test('Updates password with error', () async { + when( + () => client.post( + UserResource.kPasswordConfirmation, + body: { + 'email': 'email', + 'password': 'password', + 'confirmation_code': 'confirmationCode', + }, + ), + ).thenThrow(Exception()); + + expect( + () => subject.updatePassword( + const UpdatePasswordRequest( + email: 'email', + password: 'password', + confirmationCode: 'confirmationCode', + ), + ), + throwsA(isA()), + ); + }); + + test('Sends OTP successfully', () async { + when( + () => client.post( + UserResource.kCodeVerification, + body: { + 'email': 'email', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 201))); + + await subject.sendOtpCode( + 'email', + ); + + verify( + () => client.post( + UserResource.kCodeVerification, + body: { + 'email': 'email', + }, + ), + ).called(1); + }); + + test('Sends OTP failure with response code != 201', () async { + when( + () => client.post( + UserResource.kCodeVerification, + body: { + 'email': 'email', + }, + ), + ).thenAnswer((_) async => Future.value(Response('', 200))); + + expect( + () => subject.sendOtpCode( + 'email', + ), + throwsA(isA()), + ); + }); + + test('Sends OTP with error', () async { + when( + () => client.post( + UserResource.kCodeVerification, + body: { + 'email': 'email', + }, + ), + ).thenThrow(Exception()); + + expect( + () => subject.sendOtpCode( + 'email', + ), + throwsA(isA()), + ); + }); + + test('Gets authenticated user successfully', () async { + when( + () => client.authenticatedGet(UserResource.kUser), + ).thenAnswer( + (_) async => Future.value( + Response( + json.encode({ + 'id': 'id', + 'email': 'email', + 'name': 'name', + }), + 200, + ), + ), + ); + + await subject.getAuthenticatedUser(); + + verify( + () => client.authenticatedGet(UserResource.kUser), + ).called(1); + }); + + test('Gets authenticated user failure with response code != 200', () async { + when( + () => client.authenticatedGet(UserResource.kUser), + ).thenAnswer( + (_) async => Future.value( + Response( + json.encode({ + 'id': 'id', + 'email': 'email', + 'name': 'name', + }), + 400, + ), + ), + ); + + expect( + () => subject.getAuthenticatedUser(), + throwsA(isA()), + ); + }); + + test('Gets authenticated user with error', () async { + when( + () => client.post(UserResource.kUser), + ).thenThrow(Exception()); + + expect( + () => subject.getAuthenticatedUser(), + throwsA(isA()), + ); + }); + }); +} diff --git a/todo/packages/api_client/test/src/utils/utils_test.dart b/todo/packages/api_client/test/src/utils/utils_test.dart new file mode 100644 index 0000000..fec3dbe --- /dev/null +++ b/todo/packages/api_client/test/src/utils/utils_test.dart @@ -0,0 +1,22 @@ +import 'package:api_client/api_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('Utils', () { + test('tryToListStringToString returns null for null input', () { + expect(Utils.tryToListStringToString(null), isNull); + }); + + test('tryToListStringToString returns string separated by commas', () { + final input = ['a', 'b', 'c']; + const expected = 'a,b,c'; + expect(Utils.tryToListStringToString(input), equals(expected)); + }); + + test('tryToListStringToString returns empty string for empty list', () { + final input = []; + const expected = ''; + expect(Utils.tryToListStringToString(input), equals(expected)); + }); + }); +} diff --git a/todo/packages/app_config_repository/analysis_options.yaml b/todo/packages/app_config_repository/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/app_config_repository/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/app_config_repository/build.yaml b/todo/packages/app_config_repository/build.yaml new file mode 100644 index 0000000..917f456 --- /dev/null +++ b/todo/packages/app_config_repository/build.yaml @@ -0,0 +1,12 @@ +targets: + $default: + builders: + source_gen|combining_builder: + options: + ignore_for_file: + - implicit_dynamic_parameter + json_serializable: + options: + field_rename: snake + create_to_json: false + checked: true diff --git a/todo/packages/app_config_repository/lib/app_config_repository.dart b/todo/packages/app_config_repository/lib/app_config_repository.dart new file mode 100644 index 0000000..ce33380 --- /dev/null +++ b/todo/packages/app_config_repository/lib/app_config_repository.dart @@ -0,0 +1,5 @@ +/// An App Config implementation +library app_config_repository; + +export 'src/app_config_repository.dart'; +export 'src/models/models.dart' hide AppConfig; diff --git a/todo/packages/app_config_repository/lib/src/app_config_repository.dart b/todo/packages/app_config_repository/lib/src/app_config_repository.dart new file mode 100644 index 0000000..4c29afd --- /dev/null +++ b/todo/packages/app_config_repository/lib/src/app_config_repository.dart @@ -0,0 +1,56 @@ +import 'dart:async'; +import 'package:api_client/api_client.dart'; +import 'package:app_config_repository/src/models/models.dart'; + +/// {@template app_config_repository} +/// Repository which manages determining whether the application +/// is in maintenance mode or needs to be force upgraded. +/// {@endtemplate} +class AppConfigRepository { + /// {@macro app_config_repository} + AppConfigRepository({ + required int buildNumber, + required Platform platform, + required ApiClient apiClient, + }) : assert(buildNumber > 0, 'The build number must be greater than 0.'), + _buildNumber = buildNumber, + _platform = platform, + _apiClient = apiClient; + + final ApiClient _apiClient; + final int _buildNumber; + final Platform _platform; + + /// Returns a [Stream] which indicates whether + /// the current application status is down for maintenance. + /// + /// By default, [isDownForMaintenance] will emit `false` + /// if unable to connected to the backend. + Stream isDownForMaintenance() { + return _apiClient.appConfigResource.getDownForMaintenance().asStream(); + } + + /// Returns a [Stream] which indicates whether + /// the current application requires a force upgrade. + Stream isForceUpgradeRequired() { + var isUpgradeRequired = false; + var upgradeUrl = ''; + + _apiClient.appConfigResource.getUpgrade().then((upgrade) { + if (_platform == Platform.android) { + isUpgradeRequired = _buildNumber < upgrade.androidBuildNumber; + upgradeUrl = upgrade.androidUpgradeUrl; + } else { + isUpgradeRequired = _buildNumber < upgrade.iosBuildNumber; + upgradeUrl = upgrade.iosUpgradeUrl; + } + }); + + return Stream.value( + ForceUpgrade( + isUpgradeRequired: isUpgradeRequired, + upgradeUrl: isUpgradeRequired ? upgradeUrl : '', + ), + ); + } +} diff --git a/todo/packages/app_config_repository/lib/src/models/app_config.dart b/todo/packages/app_config_repository/lib/src/models/app_config.dart new file mode 100644 index 0000000..294b76b --- /dev/null +++ b/todo/packages/app_config_repository/lib/src/models/app_config.dart @@ -0,0 +1,49 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'app_config.g.dart'; + +/// {@template app_config} +/// Application Configuration which contains the relevant +/// information needed to determine whether or not the application is +/// down for maintenance or requires a force upgrade. +/// {@endtemplate} +@JsonSerializable() +class AppConfig extends Equatable { + /// {@macro app_config} + const AppConfig({ + required this.androidUpgradeUrl, + required this.downForMaintenance, + required this.iosUpgradeUrl, + required this.minAndroidBuildNumber, + required this.minIosBuildNumber, + }); + + /// Converts a [Map] into an [AppConfig] instance. + factory AppConfig.fromJson(Map json) => + _$AppConfigFromJson(json); + + /// Whether the app is down for maintenance. + final bool downForMaintenance; + + /// The minimum supported build number for Android. + final int minAndroidBuildNumber; + + /// The minimum supported build number for iOS. + final int minIosBuildNumber; + + /// The upgrade URL for Android. + final String androidUpgradeUrl; + + /// The upgrade URL for iOS. + final String iosUpgradeUrl; + + @override + List get props => [ + androidUpgradeUrl, + downForMaintenance, + iosUpgradeUrl, + minAndroidBuildNumber, + minIosBuildNumber, + ]; +} diff --git a/todo/packages/app_config_repository/lib/src/models/app_config.g.dart b/todo/packages/app_config_repository/lib/src/models/app_config.g.dart new file mode 100644 index 0000000..797b921 --- /dev/null +++ b/todo/packages/app_config_repository/lib/src/models/app_config.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter + +part of 'app_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppConfig _$AppConfigFromJson(Map json) { + return $checkedNew('AppConfig', json, () { + final val = AppConfig( + androidUpgradeUrl: + $checkedConvert(json, 'android_upgrade_url', (v) => v as String), + downForMaintenance: + $checkedConvert(json, 'down_for_maintenance', (v) => v as bool), + iosUpgradeUrl: + $checkedConvert(json, 'ios_upgrade_url', (v) => v as String), + minAndroidBuildNumber: + $checkedConvert(json, 'min_android_build_number', (v) => v as int), + minIosBuildNumber: + $checkedConvert(json, 'min_ios_build_number', (v) => v as int), + ); + return val; + }, fieldKeyMap: const { + 'androidUpgradeUrl': 'android_upgrade_url', + 'downForMaintenance': 'down_for_maintenance', + 'iosUpgradeUrl': 'ios_upgrade_url', + 'minAndroidBuildNumber': 'min_android_build_number', + 'minIosBuildNumber': 'min_ios_build_number' + }); +} diff --git a/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart b/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart new file mode 100644 index 0000000..2074961 --- /dev/null +++ b/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +/// Platform which is used to determine whether an upgrade is required. +enum Platform { + /// Google's OS for mobile and tablet. + android, + + /// Apple's OS for iPhone and iPad. + iOS; + + /// Whether the current platform is [Platform.android]. + bool get isAndroid => this == Platform.android; + + /// Whether the current platform is [Platform.iOS]. + bool get isIos => this == Platform.iOS; +} + +/// {@template force_upgrade} +/// Model used to encapsulate configuration information +/// regarding force upgrade. +/// +/// Includes a `bool` [isUpgradeRequired] which determines if +/// the current application requires a force upgrade as well as +/// an [upgradeUrl] to link to the latest version of the application. +/// {@endtemplate} +class ForceUpgrade extends Equatable { + /// {@macro force_upgrade} + const ForceUpgrade({ + required this.isUpgradeRequired, + this.upgradeUrl, + }); + + /// Whether an upgrade is required. + final bool isUpgradeRequired; + + /// The url where users can upgrade the application. + final String? upgradeUrl; + + @override + List get props => [isUpgradeRequired, upgradeUrl]; +} diff --git a/todo/packages/app_config_repository/lib/src/models/models.dart b/todo/packages/app_config_repository/lib/src/models/models.dart new file mode 100644 index 0000000..88bfe03 --- /dev/null +++ b/todo/packages/app_config_repository/lib/src/models/models.dart @@ -0,0 +1,2 @@ +export 'app_config.dart'; +export 'force_upgrade.dart'; diff --git a/todo/packages/app_config_repository/pubspec.yaml b/todo/packages/app_config_repository/pubspec.yaml new file mode 100644 index 0000000..8b3f714 --- /dev/null +++ b/todo/packages/app_config_repository/pubspec.yaml @@ -0,0 +1,21 @@ +name: app_config_repository +description: Dart package which manages the app configuration domain. +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + api_client: + path: ../api_client + equatable: ^2.0.5 + json_annotation: ^4.9.0 + +dev_dependencies: + build_runner: ^2.4.13 + flutter_test: + sdk: flutter + json_serializable: ^6.8.0 + mocktail: ^1.0.4 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/app_config_repository/test/app_config_repository_test.dart b/todo/packages/app_config_repository/test/app_config_repository_test.dart new file mode 100644 index 0000000..64d310e --- /dev/null +++ b/todo/packages/app_config_repository/test/app_config_repository_test.dart @@ -0,0 +1,87 @@ +import 'package:api_client/api_client.dart'; +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockApiClient extends Mock implements ApiClient {} + +class MockAppConfigResource extends Mock implements AppConfigResource {} + +void main() { + late AppConfigRepository repository; + late AppConfigRepository repositoryForiOS; + late ApiClient mockApiClient; + late AppConfigResource configResource; + + setUp(() { + mockApiClient = MockApiClient(); + configResource = MockAppConfigResource(); + repository = AppConfigRepository( + buildNumber: 10, + platform: Platform.android, + apiClient: mockApiClient, + ); + repositoryForiOS = AppConfigRepository( + buildNumber: 10, + platform: Platform.iOS, + apiClient: mockApiClient, + ); + }); + + group('AppConfigRepository', () { + test('isDownForMaintenance returns stream of booleans', () { + when(() => mockApiClient.appConfigResource).thenReturn(configResource); + when(() => configResource.getDownForMaintenance()).thenAnswer( + (_) => Future.value(false), + ); + + final stream = repository.isDownForMaintenance(); + + expect(stream, emits(false)); + }); + + test('isForceUpgradeRequired handles iOS platform', () { + const mockUpgrade = Upgrade( + androidBuildNumber: 20, + iosBuildNumber: 20, + androidUpgradeUrl: 'https://example.com/android', + iosUpgradeUrl: 'https://example.com/ios', + ); + + const mockForceUpgrade = ForceUpgrade( + isUpgradeRequired: false, + upgradeUrl: '', + ); + + final mockResponse = Future.value(mockUpgrade); + when(() => mockApiClient.appConfigResource).thenReturn(configResource); + when(() => configResource.getUpgrade()).thenAnswer((_) => mockResponse); + + final stream = repository.isForceUpgradeRequired(); + + expect(stream, emits(mockForceUpgrade)); + }); + + test('isForceUpgradeRequired handles iOS platform', () { + const mockUpgrade = Upgrade( + androidBuildNumber: 20, + iosBuildNumber: 20, + androidUpgradeUrl: 'https://example.com/android', + iosUpgradeUrl: 'https://example.com/ios', + ); + + const mockForceUpgrade = ForceUpgrade( + isUpgradeRequired: false, + upgradeUrl: '', + ); + + final mockResponse = Future.value(mockUpgrade); + when(() => mockApiClient.appConfigResource).thenReturn(configResource); + when(() => configResource.getUpgrade()).thenAnswer((_) => mockResponse); + + final stream = repositoryForiOS.isForceUpgradeRequired(); + + expect(stream, emits(mockForceUpgrade)); + }); + }); +} diff --git a/todo/packages/app_config_repository/test/models/app_config_test.dart b/todo/packages/app_config_repository/test/models/app_config_test.dart new file mode 100644 index 0000000..2047441 --- /dev/null +++ b/todo/packages/app_config_repository/test/models/app_config_test.dart @@ -0,0 +1,86 @@ +import 'package:app_config_repository/src/models/models.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AppConfig', () { + test('equatable props', () { + const appConfig1 = AppConfig( + androidUpgradeUrl: 'https://example.com/android', + downForMaintenance: true, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 100, + minIosBuildNumber: 200, + ); + + const appConfig2 = AppConfig( + androidUpgradeUrl: 'https://example.com/android', + downForMaintenance: true, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 100, + minIosBuildNumber: 200, + ); + + const appConfig3 = AppConfig( + androidUpgradeUrl: 'https://different-url.com/android', + downForMaintenance: true, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 100, + minIosBuildNumber: 200, + ); + + expect(appConfig1, equals(appConfig2)); + expect(appConfig1 == appConfig3, isFalse); + }); + + test('fromJson', () { + final json = { + 'android_upgrade_url': 'https://example.com/android', + 'down_for_maintenance': true, + 'ios_upgrade_url': 'https://example.com/ios', + 'min_android_build_number': 100, + 'min_ios_build_number': 100, + }; + + final appConfig = AppConfig.fromJson(json); + + expect(appConfig.androidUpgradeUrl, equals(json['android_upgrade_url'])); + expect( + appConfig.downForMaintenance, + equals(json['down_for_maintenance']), + ); + expect(appConfig.iosUpgradeUrl, equals(json['ios_upgrade_url'])); + expect( + appConfig.minAndroidBuildNumber, + equals(json['min_android_build_number']), + ); + expect(appConfig.minIosBuildNumber, equals(json['min_ios_build_number'])); + }); + + test('supports value equality', () { + const appConfig = AppConfig( + androidUpgradeUrl: 'https://example.com/android', + downForMaintenance: true, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 100, + minIosBuildNumber: 200, + ); + const secondAppConfig = AppConfig( + androidUpgradeUrl: 'https://example.com/android', + downForMaintenance: true, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 100, + minIosBuildNumber: 200, + ); + const appConfig2 = AppConfig( + androidUpgradeUrl: 'https://different-url.com/android', + downForMaintenance: false, + iosUpgradeUrl: 'https://example.com/ios', + minAndroidBuildNumber: 200, + minIosBuildNumber: 200, + ); + + expect(appConfig, equals(secondAppConfig)); + expect(appConfig, isNot(equals(appConfig2))); + }); + }); +} diff --git a/todo/packages/app_config_repository/test/models/force_upgrade_test.dart b/todo/packages/app_config_repository/test/models/force_upgrade_test.dart new file mode 100644 index 0000000..7f8dd0c --- /dev/null +++ b/todo/packages/app_config_repository/test/models/force_upgrade_test.dart @@ -0,0 +1,53 @@ +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Platform', () { + test('isAndroid should return true for Platform.android', () { + expect(Platform.android.isAndroid, isTrue); + expect(Platform.android.isIos, isFalse); + }); + + test('isIos should return true for Platform.iOS', () { + expect(Platform.iOS.isAndroid, isFalse); + expect(Platform.iOS.isIos, isTrue); + }); + + test('Platform equality', () { + expect(Platform.android, equals(Platform.android)); + expect(Platform.iOS, equals(Platform.iOS)); + expect(Platform.android == Platform.iOS, isFalse); + }); + }); + + group('ForceUpgrade', () { + test('equality', () { + const upgrade1 = ForceUpgrade( + isUpgradeRequired: true, + upgradeUrl: 'https://example.com/upgrade1', + ); + + const upgrade2 = ForceUpgrade( + isUpgradeRequired: true, + upgradeUrl: 'https://example.com/upgrade1', + ); + + const upgrade3 = ForceUpgrade( + isUpgradeRequired: true, + upgradeUrl: 'https://example.com/upgrade2', + ); + + expect(upgrade1, equals(upgrade2)); + expect(upgrade1 == upgrade3, isFalse); + }); + + test('props should include all fields', () { + const upgrade = ForceUpgrade( + isUpgradeRequired: true, + upgradeUrl: 'https://example.com/upgrade', + ); + + expect(upgrade.props, containsAll([true, 'https://example.com/upgrade'])); + }); + }); +} diff --git a/todo/packages/auth_client/.gitignore b/todo/packages/auth_client/.gitignore new file mode 100644 index 0000000..ea1d8dc --- /dev/null +++ b/todo/packages/auth_client/.gitignore @@ -0,0 +1,4 @@ +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/todo/packages/auth_client/analysis_options.yaml b/todo/packages/auth_client/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/auth_client/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/auth_client/lib/auth_client.dart b/todo/packages/auth_client/lib/auth_client.dart new file mode 100644 index 0000000..6d7c598 --- /dev/null +++ b/todo/packages/auth_client/lib/auth_client.dart @@ -0,0 +1,6 @@ +/// An authentication client implementation +library auth_client; + +export 'src/auth_client.dart'; +export 'src/exceptions/exceptions.dart'; +export 'src/models/models.dart'; diff --git a/todo/packages/auth_client/lib/src/auth_client.dart b/todo/packages/auth_client/lib/src/auth_client.dart new file mode 100644 index 0000000..79ca702 --- /dev/null +++ b/todo/packages/auth_client/lib/src/auth_client.dart @@ -0,0 +1,97 @@ +import 'dart:async'; + +import 'package:auth_client/auth_client.dart'; +import 'package:rxdart/subjects.dart'; +import 'package:token_provider/token_provider.dart'; + +/// {@template auth_client} +/// An authentication client implementation baked with [Auth Service] +/// {@endtemplate} +class AuthenticationClient implements TokenProvider { + /// {@macro auth_client} + AuthenticationClient() + : authEventSubject = BehaviorSubject() { + _initialize(); + } + + /// A [BehaviorSubject] that emits [AuthenticationEvent]s. + final BehaviorSubject authEventSubject; + + Future _initialize() async { + authEventSubject.add( + const AuthenticationEvent( + type: AuthEventType.signedOut, + ), + ); + } + + /// Emits a new event when the [AuthEventType] status changes. + Stream get onAuthStatusChanged { + return authEventSubject.stream; + } + + /// Initiates the sign in process with [email] and [password]. + /// + /// If an error occurs, it throws one of the following [Exception]s: + /// - [InvalidUserFailure] in case the user wasn't found + /// - [SignInFailure] in case of an unknown error + Future signInUser({ + required String email, + required String password, + }) async { + try { + const userId = 'userId'; + authEventSubject.add( + AuthenticationEvent( + type: AuthEventType.signedIn, + user: AuthenticationUser( + id: userId, + email: email, + ), + ), + ); + } catch (error) { + authEventSubject.add( + const AuthenticationEvent( + type: AuthEventType.signedOut, + ), + ); + } + } + + @override + Future fetchToken() async { + return 'token'; + } + + @override + Future requestTokenRefresh() { + throw UnimplementedError(); + } + + /// Signs out the current user. + Future signOut() async { + authEventSubject.add( + const AuthenticationEvent( + type: AuthEventType.signedOut, + ), + ); + } + + /// Re-authenticates the current user. + Future reAuthenticate({ + required String email, + required String password, + }) { + throw UnimplementedError(); + } + + /// Deletes the current user. + Future deleteAccount() async { + authEventSubject.add( + const AuthenticationEvent( + type: AuthEventType.userDeleted, + ), + ); + } +} diff --git a/todo/packages/auth_client/lib/src/exceptions/exceptions.dart b/todo/packages/auth_client/lib/src/exceptions/exceptions.dart new file mode 100644 index 0000000..976aca0 --- /dev/null +++ b/todo/packages/auth_client/lib/src/exceptions/exceptions.dart @@ -0,0 +1,63 @@ +import 'package:auth_client/auth_client.dart'; + +/// {@template authentication_client_exception} +/// Base class for all [AuthenticationClient] exceptions. +/// {@endtemplate} +abstract class AuthenticationClientException implements Exception { + /// {@macro authentication_client_exception} + const AuthenticationClientException( + this.error, + this.stackTrace, + ); + + /// The error which was caught. + final Object error; + + /// The error stack trace. + final StackTrace stackTrace; +} + +/// {@template sign_in_failure} +/// Thrown when an error occurs on the sign in process. +/// {@endtemplate} +class SignInFailure extends AuthenticationClientException { + /// {@macro sign_in_failure} + const SignInFailure( + super.error, + super.stackTrace, + ); +} + +/// {@template invalid_user_failure} +/// Thrown when an error occurs on the sign in process because the user +/// was not found. +/// {@endtemplate} +class InvalidUserFailure extends AuthenticationClientException { + /// {@macro invalid_user_failure} + const InvalidUserFailure( + super.error, + super.stackTrace, + ); +} + +/// {@template reauthenticate_failure} +/// Thrown when an error occurs on the reauthentication process. +/// {@endtemplate} +class ReAuthenticateFailure extends AuthenticationClientException { + /// {@macro reauthenticate_failure} + const ReAuthenticateFailure( + super.error, + super.stackTrace, + ); +} + +/// {@template delete_account_failure} +/// Thrown when an error occurs on the delete account process. +/// {@endtemplate} +class DeleteAccountFailure extends AuthenticationClientException { + /// {@macro delete_account_failure} + const DeleteAccountFailure( + super.error, + super.stackTrace, + ); +} diff --git a/todo/packages/auth_client/lib/src/models/authentication_event.dart b/todo/packages/auth_client/lib/src/models/authentication_event.dart new file mode 100644 index 0000000..b988aa2 --- /dev/null +++ b/todo/packages/auth_client/lib/src/models/authentication_event.dart @@ -0,0 +1,52 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:equatable/equatable.dart'; + +/// {@template auth_event_type} +/// Model representing the different event types regarding the authentication +/// status. +/// {@endtemplate} +enum AuthEventType { + /// Signed in event. + signedIn, + + /// Signed out event. + signedOut, + + /// Session expired event. + sessionExpired, + + /// User deleted event. + userDeleted; + + /// Whether `this` is equal to [signedIn]. + bool get isSignedIn => this == signedIn; + + /// Whether `this` is equal to [signedOut]. + bool get isSignedOut => this == signedOut; + + /// Whether `this` is equal to [sessionExpired]. + bool get isSessionExpired => this == sessionExpired; + + /// Whether `this` is equal to [userDeleted]. + bool get isUserDeleted => this == userDeleted; +} + +/// {@template authentication_event} +/// Model representing an authentication event. +/// {@endtemplate} +class AuthenticationEvent extends Equatable { + /// {@macro authentication_event} + const AuthenticationEvent({ + required this.type, + AuthenticationUser? user, + }) : user = user ?? AuthenticationUser.anonymous; + + /// The authentication user. + final AuthenticationUser user; + + /// The authentication event type. + final AuthEventType type; + + @override + List get props => [user, type]; +} diff --git a/todo/packages/auth_client/lib/src/models/authentication_user.dart b/todo/packages/auth_client/lib/src/models/authentication_user.dart new file mode 100644 index 0000000..e2cf3f3 --- /dev/null +++ b/todo/packages/auth_client/lib/src/models/authentication_user.dart @@ -0,0 +1,30 @@ +import 'package:equatable/equatable.dart'; + +/// {@template authentication_user} +/// Model representing a User from the authentication domain. +/// {@endtemplate} +class AuthenticationUser extends Equatable { + /// {@macro authentication_user} + const AuthenticationUser({ + required this.id, + required this.email, + }); + + /// An anonymous user representing the signed out state. + static const anonymous = AuthenticationUser( + id: '', + email: '', + ); + + /// Authentication user id. + final String id; + + /// Authentication user email address. + final String email; + + @override + List get props => [id, email]; + + /// Whether `this` is equal to [anonymous]. + bool get isAnonymous => this == anonymous; +} diff --git a/todo/packages/auth_client/lib/src/models/models.dart b/todo/packages/auth_client/lib/src/models/models.dart new file mode 100644 index 0000000..fc06f78 --- /dev/null +++ b/todo/packages/auth_client/lib/src/models/models.dart @@ -0,0 +1,2 @@ +export 'authentication_event.dart'; +export 'authentication_user.dart'; diff --git a/todo/packages/auth_client/pubspec.yaml b/todo/packages/auth_client/pubspec.yaml new file mode 100644 index 0000000..3e45917 --- /dev/null +++ b/todo/packages/auth_client/pubspec.yaml @@ -0,0 +1,18 @@ +name: auth_client +description: An authentication client implementation +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + equatable: ^2.0.5 + rxdart: ^0.28.0 + token_provider: + path: ../token_provider + +dev_dependencies: + mocktail: ^1.0.4 + test: ^1.25.8 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/auth_client/test/src/auth_client_test.dart b/todo/packages/auth_client/test/src/auth_client_test.dart new file mode 100644 index 0000000..a4f4558 --- /dev/null +++ b/todo/packages/auth_client/test/src/auth_client_test.dart @@ -0,0 +1,74 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('AuthClient', () { + late AuthenticationClient repository; + + const validEmail = 'email@example.com'; + const validPassword = 'password'; + + setUp(() { + repository = AuthenticationClient(); + }); + + test('can be instantiated', () { + final repository = AuthenticationClient(); + expect(repository, isNotNull); + expect(repository, isA()); + }); + + test('signs in user correctly', () { + expect( + () async => repository.signInUser( + email: validEmail, + password: validPassword, + ), + returnsNormally, + ); + }); + + test('calls auth status change on AuthenticationClient', () { + final stream = repository.onAuthStatusChanged; + expect(stream, isA>()); + }); + + test('signs out user correctly', () { + expect( + () async => repository.signOut(), + returnsNormally, + ); + }); + + test('fetch user token correctly', () { + expect( + () async => repository.fetchToken(), + returnsNormally, + ); + }); + + test('refreshes user token correctly', () { + expect( + () async => repository.requestTokenRefresh(), + throwsUnimplementedError, + ); + }); + + test('reauthenticates user', () { + expect( + () async => repository.reAuthenticate( + email: validEmail, + password: validPassword, + ), + throwsUnimplementedError, + ); + }); + + test('deletes account', () { + expect( + () async => repository.deleteAccount(), + returnsNormally, + ); + }); + }); +} diff --git a/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart b/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart new file mode 100644 index 0000000..969fce9 --- /dev/null +++ b/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart @@ -0,0 +1,52 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('SignInFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Invalid arguments'); + final stackTrace = StackTrace.current; + + final signInFailure = SignInFailure(error, stackTrace); + + expect(signInFailure.error, equals(error)); + expect(signInFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('InvalidUserFailure', () { + test('constructs correctly', () { + final error = StateError('User not found'); + final stackTrace = StackTrace.current; + + final invalidUserFailure = InvalidUserFailure(error, stackTrace); + + expect(invalidUserFailure.error, equals(error)); + expect(invalidUserFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('ReAuthenticateFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Could not reauthenticate'); + final stackTrace = StackTrace.current; + + final reauthenticateFailure = ReAuthenticateFailure(error, stackTrace); + + expect(reauthenticateFailure.error, equals(error)); + expect(reauthenticateFailure.stackTrace, equals(stackTrace)); + }); + }); + + group('DeleteAccountFailure', () { + test('constructs correctly', () { + final error = ArgumentError('Invalid arguments'); + final stackTrace = StackTrace.current; + + final deleteAccountFailure = DeleteAccountFailure(error, stackTrace); + + expect(deleteAccountFailure.error, equals(error)); + expect(deleteAccountFailure.stackTrace, equals(stackTrace)); + }); + }); +} diff --git a/todo/packages/auth_client/test/src/models/authentication_event_test.dart b/todo/packages/auth_client/test/src/models/authentication_event_test.dart new file mode 100644 index 0000000..aa8cadf --- /dev/null +++ b/todo/packages/auth_client/test/src/models/authentication_event_test.dart @@ -0,0 +1,82 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('AuthEventType', () { + test('isSignedIn returns true for signedIn event', () { + final isSignedIn = AuthEventType.signedIn.isSignedIn; + expect(isSignedIn, isTrue); + }); + + test('isSignedOut returns true for signedIn event', () { + final isSignedOut = AuthEventType.signedOut.isSignedOut; + expect(isSignedOut, isTrue); + }); + + test('isSessionExpired returns true for signedIn event', () { + final isSessionExpired = AuthEventType.sessionExpired.isSessionExpired; + expect(isSessionExpired, isTrue); + }); + + test('isUserDeleted returns true for signedIn event', () { + final isUserDeleted = AuthEventType.userDeleted.isUserDeleted; + expect(isUserDeleted, isTrue); + }); + }); + + group('AuthenticationEvent', () { + test('constructor initializes properties', () { + const user = AuthenticationUser(id: '123', email: 'test@example.com'); + const type = AuthEventType.signedIn; + + const event = AuthenticationEvent(type: type, user: user); + + expect(event.user, equals(user)); + expect(event.type, equals(type)); + }); + + test('constructor initializes user as anonymous if null', () { + const type = AuthEventType.signedOut; + + const event = AuthenticationEvent(type: type); + + expect(event.user, equals(AuthenticationUser.anonymous)); + expect(event.type, equals(type)); + }); + + test('props list contains user and type', () { + const user = AuthenticationUser(id: '123', email: 'test@example.com'); + const type = AuthEventType.signedIn; + + const event = AuthenticationEvent(type: type, user: user); + + expect(event.props, containsAll([user, type])); + }); + + test('supports value equality', () { + const eventA = AuthenticationEvent( + user: AuthenticationUser(id: 'A', email: 'A'), + type: AuthEventType.signedIn, + ); + const secondEventA = AuthenticationEvent( + user: AuthenticationUser(id: 'A', email: 'A'), + type: AuthEventType.signedIn, + ); + const eventB = AuthenticationEvent( + user: AuthenticationUser(id: 'B', email: 'B'), + type: AuthEventType.signedIn, + ); + + expect(eventA, equals(secondEventA)); + expect(eventA, isNot(equals(eventB))); + }); + + test('constructor initializes user as anonymous if user is null', () { + const type = AuthEventType.signedOut; + const event = AuthenticationEvent(type: type); + + expect(event.user, equals(AuthenticationUser.anonymous)); + expect(event.type, equals(type)); + }); + }); +} diff --git a/todo/packages/auth_client/test/src/models/authentication_user_test.dart b/todo/packages/auth_client/test/src/models/authentication_user_test.dart new file mode 100644 index 0000000..d6293f1 --- /dev/null +++ b/todo/packages/auth_client/test/src/models/authentication_user_test.dart @@ -0,0 +1,54 @@ +import 'package:auth_client/auth_client.dart'; +import 'package:test/test.dart'; + +void main() { + group('AuthenticationUser', () { + test('constructor initializes properties', () { + const id = '123'; + const email = 'test@example.com'; + + const user = AuthenticationUser(id: id, email: email); + + expect(user.id, equals(id)); + expect(user.email, equals(email)); + }); + + test('anonymous user is initialized correctly', () { + const user = AuthenticationUser.anonymous; + + expect(user.id, isEmpty); + expect(user.email, isEmpty); + }); + + test('props list contains id and email', () { + const id = '123'; + const email = 'test@example.com'; + + const user = AuthenticationUser(id: id, email: email); + + expect(user.props, containsAll([id, email])); + }); + + test('supports value equality', () { + const userA = AuthenticationUser(id: 'A', email: 'A'); + const secondUserA = AuthenticationUser(id: 'A', email: 'A'); + const userB = AuthenticationUser(id: 'B', email: 'B'); + + expect(userA, equals(secondUserA)); + expect(userA, isNot(equals(userB))); + }); + + test('isAnonymous returns true for anonymous user', () { + expect(AuthenticationUser.anonymous.isAnonymous, isTrue); + }); + + test( + 'constructor initializes properties with ' + 'empty strings if values are empty', () { + const user = AuthenticationUser.anonymous; + + expect(user.id, isEmpty); + expect(user.email, isEmpty); + }); + }); +} diff --git a/todo/packages/form_inputs/.gitignore b/todo/packages/form_inputs/.gitignore new file mode 100644 index 0000000..d613035 --- /dev/null +++ b/todo/packages/form_inputs/.gitignore @@ -0,0 +1,39 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VSCode related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/todo/packages/form_inputs/LICENSE b/todo/packages/form_inputs/LICENSE new file mode 100644 index 0000000..6f19952 --- /dev/null +++ b/todo/packages/form_inputs/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Somnio Software + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/todo/packages/form_inputs/README.md b/todo/packages/form_inputs/README.md new file mode 100644 index 0000000..1cb3921 --- /dev/null +++ b/todo/packages/form_inputs/README.md @@ -0,0 +1,3 @@ +# form_inputs + +A Dart package which exposes reusable form inputs and validation rules diff --git a/todo/packages/form_inputs/analysis_options.yaml b/todo/packages/form_inputs/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/form_inputs/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/form_inputs/lib/form_inputs.dart b/todo/packages/form_inputs/lib/form_inputs.dart new file mode 100644 index 0000000..c7fe029 --- /dev/null +++ b/todo/packages/form_inputs/lib/form_inputs.dart @@ -0,0 +1,11 @@ +export 'package:formz/formz.dart'; + +export 'src/address_label.dart'; +export 'src/email.dart'; +export 'src/form_status_enum.dart'; +export 'src/name.dart'; +export 'src/otp_code.dart'; +export 'src/password.dart'; +export 'src/phone_number.dart'; +export 'src/username.dart'; +export 'src/zipcode.dart'; diff --git a/todo/packages/form_inputs/lib/src/address_label.dart b/todo/packages/form_inputs/lib/src/address_label.dart new file mode 100644 index 0000000..16adfba --- /dev/null +++ b/todo/packages/form_inputs/lib/src/address_label.dart @@ -0,0 +1,34 @@ +import 'package:form_inputs/form_inputs.dart'; + +/// Address Label Form Input Validation Error +enum AddressLabelValidationError { + /// Address Label is invalid (generic validation error) + invalid, + + /// Address Label is empty + empty, +} + +/// {@template address_label} +/// Reusable address label form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class AddressLabel extends FormzInput { + /// {@macro address_label} + /// Call super.pure to represent an unmodified form input. + const AddressLabel.pure() : super.pure(''); + + /// {@macro address_label} + /// Call super.dirty to represent a modified form input. + const AddressLabel.dirty([super.value = '']) : super.dirty(); + + /// Override validator to handle validating a given input value. + @override + AddressLabelValidationError? validator(String? value) { + if (value == null || value.trim().isEmpty) { + return AddressLabelValidationError.empty; + } + if (value.trim().isNotEmpty) return null; + return AddressLabelValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/lib/src/email.dart b/todo/packages/form_inputs/lib/src/email.dart new file mode 100644 index 0000000..6e472bc --- /dev/null +++ b/todo/packages/form_inputs/lib/src/email.dart @@ -0,0 +1,45 @@ +import 'package:equatable/equatable.dart'; +import 'package:formz/formz.dart'; + +/// Email Form Input Validation Error +enum EmailValidationError { + /// Email is invalid (generic validation error) + invalid, + + /// Email is empty + empty, +} + +/// {@template email} +/// Reusable email form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class Email extends FormzInput + with EquatableMixin { + /// {@macro email} + /// Call super.pure to represent an unmodified form input. + const Email.pure() : super.pure(''); + + /// {@macro email} + /// Call super.dirty to represent a modified form input. + const Email.dirty([super.value = '']) : super.dirty(); + + static final RegExp _emailRegExp = RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$', + ); + + /// Override validator to handle validating a given input value. + @override + EmailValidationError? validator(String value) { + if (value.isEmpty) { + return EmailValidationError.empty; + } + if (!_emailRegExp.hasMatch(value)) { + return EmailValidationError.invalid; + } + return null; + } + + @override + List get props => [value]; +} diff --git a/todo/packages/form_inputs/lib/src/form_status_enum.dart b/todo/packages/form_inputs/lib/src/form_status_enum.dart new file mode 100644 index 0000000..4e69470 --- /dev/null +++ b/todo/packages/form_inputs/lib/src/form_status_enum.dart @@ -0,0 +1,20 @@ +/// Enum to manage the state of a form +enum FormStatus { + /// Used when all the inputs are pure + pure, + + /// Used when all the inputs are valid + valid, + + /// Used when one or more inputs are invalid + invalid, + + /// Used when one or more inputs are dirty + submissionInProgress, + + /// Used when form is waiting for an external process + submissionLoading, + + /// Used when form is valid and all external process finished + submissionLoaded, +} diff --git a/todo/packages/form_inputs/lib/src/name.dart b/todo/packages/form_inputs/lib/src/name.dart new file mode 100644 index 0000000..65d42cf --- /dev/null +++ b/todo/packages/form_inputs/lib/src/name.dart @@ -0,0 +1,40 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:formz/formz.dart'; + +/// Name Form Input Validation Error +enum NameValidationError { + /// Name is invalid (generic validation error) + invalid, + + /// Name is empty + empty, +} + +/// {@template name} +/// Reusable name form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class Name extends FormzInput { + /// {@macro name} + /// Call super.pure to represent an unmodified form input. + const Name.pure() : super.pure(''); + + /// {@macro name} + /// Call super.dirty to represent a modified form input. + const Name.dirty([super.value = '']) : super.dirty(); + + static final _nameRegExp = RegExp( + r"^[\p{L} ,.'-]*$", + caseSensitive: false, + unicode: true, + dotAll: true, + ); + + /// Override validator to handle validating a given input value. + @override + NameValidationError? validator(String? value) { + if (value == null || value.isEmpty) return NameValidationError.empty; + + return _nameRegExp.hasMatch(value) ? null : NameValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/lib/src/otp_code.dart b/todo/packages/form_inputs/lib/src/otp_code.dart new file mode 100644 index 0000000..21dc529 --- /dev/null +++ b/todo/packages/form_inputs/lib/src/otp_code.dart @@ -0,0 +1,31 @@ +import 'package:form_inputs/form_inputs.dart'; + +/// OTP Form Input Validation Error +enum OtpValidationError { + /// OTP is invalid (generic validation error) + invalid, + + /// OTP is empty + empty, +} + +/// {@template otp} +/// Reusable OTP form input that is valid only for 6 digits. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class Otp extends FormzInput { + /// {@macro otp} + /// Call super.pure to represent an unmodified form input. + const Otp.pure() : super.pure(''); + + /// {@macro otp} + /// Call super.dirty to represent a modified form input. + const Otp.dirty([super.value = '']) : super.dirty(); + + /// Override validator to handle validating a given input value. + @override + OtpValidationError? validator(String? value) { + if (value == null) return OtpValidationError.empty; + return value.length >= 6 ? null : OtpValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/lib/src/password.dart b/todo/packages/form_inputs/lib/src/password.dart new file mode 100644 index 0000000..228918a --- /dev/null +++ b/todo/packages/form_inputs/lib/src/password.dart @@ -0,0 +1,69 @@ +import 'package:formz/formz.dart'; + +/// Password Form Input Validation Error +enum PasswordValidationError { + /// Password is empty + empty, + + /// Password is shorter than 8 characters + notLongEnough, + + /// Password does not contain a digit + noDigit, + + /// Password does not contain an upper case + noUpperCase, + + /// Password does not contain a lower case + noLowerCase, + + /// Password does not contain a special character + noSpecialCharacter, +} + +/// {@template password} +/// Reusable password form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class Password extends FormzInput { + /// {@macro password} + /// Call super.pure to represent an unmodified form input. + const Password.pure() : super.pure(''); + + /// {@macro password} + /// Call super.dirty to represent a modified form input. + const Password.dirty([super.value = '']) : super.dirty(); + + static final RegExp _upperCaseRegex = RegExp('[A-Z]'); + + static final RegExp _lowerCaseRegex = RegExp('[a-z]'); + + static final RegExp _specialCharactersRegex = + RegExp(r'[!@#$%^&*(),.?":{}|<>]'); + + static final RegExp _digitsRegex = RegExp('[0-9]'); + + /// Override validator to handle validating a given input value. + @override + PasswordValidationError? validator(String value) { + if (value.isEmpty) { + return PasswordValidationError.empty; + } + if (value.length < 8) { + return PasswordValidationError.notLongEnough; + } + if (!value.contains(_digitsRegex)) { + return PasswordValidationError.noDigit; + } + if (!value.contains(_upperCaseRegex)) { + return PasswordValidationError.noUpperCase; + } + if (!value.contains(_lowerCaseRegex)) { + return PasswordValidationError.noLowerCase; + } + if (!value.contains(_specialCharactersRegex)) { + return PasswordValidationError.noSpecialCharacter; + } + return null; + } +} diff --git a/todo/packages/form_inputs/lib/src/phone_number.dart b/todo/packages/form_inputs/lib/src/phone_number.dart new file mode 100644 index 0000000..073aaec --- /dev/null +++ b/todo/packages/form_inputs/lib/src/phone_number.dart @@ -0,0 +1,40 @@ +import 'package:form_inputs/form_inputs.dart'; + +/// PhoneNumber Form Input Validation Error +enum PhoneNumberValidationError { + /// PhoneNumber is invalid (it can only have digits and + sign) + invalid, + + /// Phone number is too short + tooShort, + + /// Phone number is too long + tooLong, + + /// PhoneNumber is empty + empty, +} + +/// {@template phone_number} +/// Reusable PhoneNumber form input that is valid only for 10 digits. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class PhoneNumber extends FormzInput { + /// {@macro phone_number} + /// Call super.pure to represent an unmodified form input. + const PhoneNumber.pure() : super.pure(''); + + /// {@macro phone_number} + /// Call super.dirty to represent a modified form input. + const PhoneNumber.dirty([super.value = '']) : super.dirty(); + + static final _phoneRegex = RegExp(r'^[0-9]{10}$'); + + @override + PhoneNumberValidationError? validator(String value) { + if (value.isEmpty) return PhoneNumberValidationError.empty; + return _phoneRegex.hasMatch(value) + ? null + : PhoneNumberValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/lib/src/src.dart b/todo/packages/form_inputs/lib/src/src.dart new file mode 100644 index 0000000..acda98a --- /dev/null +++ b/todo/packages/form_inputs/lib/src/src.dart @@ -0,0 +1,8 @@ +export 'address_label.dart'; +export 'email.dart'; +export 'form_status_enum.dart'; +export 'name.dart'; +export 'otp_code.dart'; +export 'password.dart'; +export 'phone_number.dart'; +export 'zipcode.dart'; diff --git a/todo/packages/form_inputs/lib/src/username.dart b/todo/packages/form_inputs/lib/src/username.dart new file mode 100644 index 0000000..619cf23 --- /dev/null +++ b/todo/packages/form_inputs/lib/src/username.dart @@ -0,0 +1,42 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:formz/formz.dart'; + +/// Username Form Input Validation Error +enum UsernameValidationError { + /// Username is invalid (generic validation error) + invalid, + + /// Username is empty + empty, +} + +/// {@template username} +/// Reusable username form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class Username extends FormzInput { + /// {@macro Username} + /// Call super.pure to represent an unmodified form input. + const Username.pure() : super.pure(''); + + /// {@macro Username} + /// Call super.dirty to represent a modified form input. + const Username.dirty([super.value = '']) : super.dirty(); + + static final _usernameRegExp = RegExp( + r'^^[a-zA-Z0-9_-]+$', + caseSensitive: false, + unicode: true, + dotAll: true, + ); + + /// Override validator to handle validating a given input value. + @override + UsernameValidationError? validator(String? value) { + if (value == null || value.isEmpty) return UsernameValidationError.empty; + + return _usernameRegExp.hasMatch(value) + ? null + : UsernameValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/lib/src/zipcode.dart b/todo/packages/form_inputs/lib/src/zipcode.dart new file mode 100644 index 0000000..dec3fbb --- /dev/null +++ b/todo/packages/form_inputs/lib/src/zipcode.dart @@ -0,0 +1,48 @@ +import 'package:formz/formz.dart'; + +/// Zip Code Form Input validation error +enum ZipCodeValidationError { + /// Zip Code is invalid (generic validation error) + invalid, + + ///Zip code is empty + empty, +} + +/// {@template zip_code} +/// Reusable zip code form input. +/// Extends FormzInput and provides the input type and error type. +/// {@endtemplate} +class ZipCode extends FormzInput { + /// {@macro zip_code} + /// Call super.pure to represent an unmodified form input. + const ZipCode.pure({bool required = true}) + : _required = required, + super.pure(''); + + /// {@macro zip_code} + /// Call super.dirty to represent a modified form input. + const ZipCode.dirty({String value = '', bool required = true}) + : _required = required, + super.dirty(value); + + final bool _required; + + /// Matches 5-digit only codes (e.g. 12345) + static final RegExp _zipCodeRegExp = RegExp(r'(^\d{5}(-\d{4})?$)'); + + /// Override validator to handle validating a given input value. + @override + ZipCodeValidationError? validator(String value) { + if (!_required && value.isEmpty) { + return null; + } + if (_required && value.isEmpty) { + return ZipCodeValidationError.empty; + } + + return _zipCodeRegExp.hasMatch(value) + ? null + : ZipCodeValidationError.invalid; + } +} diff --git a/todo/packages/form_inputs/pubspec.yaml b/todo/packages/form_inputs/pubspec.yaml new file mode 100644 index 0000000..859f24e --- /dev/null +++ b/todo/packages/form_inputs/pubspec.yaml @@ -0,0 +1,14 @@ +name: form_inputs +description: A Dart package which exposes reusable form inputs and validation rules +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + equatable: ^2.0.5 + formz: ^0.7.0 + +dev_dependencies: + test: ^1.25.8 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/form_inputs/test/address_label_test.dart b/todo/packages/form_inputs/test/address_label_test.dart new file mode 100644 index 0000000..be25e04 --- /dev/null +++ b/todo/packages/form_inputs/test/address_label_test.dart @@ -0,0 +1,44 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const addressString = '1142 Main Street, Seattle, WA 98101'; + group('Address Label', () { + group('constructors', () { + test('pure creates correct instance', () { + const address = AddressLabel.pure(); + expect(address.value, ''); + expect(address.isPure, true); + }); + + test('dirty creates correct instance', () { + const address = AddressLabel.dirty(addressString); + expect(address.value, addressString); + expect(address.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when address is empty', () { + expect( + const AddressLabel.dirty().error, + AddressLabelValidationError.empty, + ); + }); + + test('returns invalid error when address is malformed', () { + expect( + const AddressLabel.dirty('test').error, + isNull, + ); + }); + + test('is valid when address is valid', () { + expect( + const AddressLabel.dirty(addressString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/email_test.dart b/todo/packages/form_inputs/test/email_test.dart new file mode 100644 index 0000000..3442198 --- /dev/null +++ b/todo/packages/form_inputs/test/email_test.dart @@ -0,0 +1,60 @@ +// ignore_for_file: prefer_const_constructors +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const emailString = 'test@gmail.com'; + group('Email', () { + group('constructors', () { + test('pure creates correct instance', () { + final email = Email.pure(); + expect(email.value, ''); + expect(email.isPure, true); + }); + + test('dirty creates correct instance', () { + final email = Email.dirty(emailString); + expect(email.value, emailString); + expect(email.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when email is empty', () { + expect( + Email.dirty().error, + EmailValidationError.empty, + ); + }); + + test('returns invalid error when email is malformed', () { + expect( + Email.dirty('test').error, + EmailValidationError.invalid, + ); + }); + + test('is valid when email is valid', () { + expect( + Email.dirty(emailString).error, + isNull, + ); + }); + + test('equality should work correctly', () { + const email1 = Email.dirty('test1@email.com'); + const email2 = Email.dirty('test1@email.com'); + const email3 = Email.dirty('test2@email.com'); + + expect( + email1, + equals(email2), + ); + expect( + email1 == email3, + isFalse, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/name_test.dart b/todo/packages/form_inputs/test/name_test.dart new file mode 100644 index 0000000..77420f5 --- /dev/null +++ b/todo/packages/form_inputs/test/name_test.dart @@ -0,0 +1,44 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const nameString = 'test'; + group('Name', () { + group('constructors', () { + test('pure creates correct instance', () { + const name = Name.pure(); + expect(name.value, ''); + expect(name.isPure, true); + }); + + test('dirty creates correct instance', () { + const name = Name.dirty(nameString); + expect(name.value, nameString); + expect(name.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when name is empty', () { + expect( + const Name.dirty().error, + NameValidationError.empty, + ); + }); + + test('returns invalid error when name is malformed', () { + expect( + const Name.dirty('test').error, + isNull, + ); + }); + + test('is valid when name is valid', () { + expect( + const Name.dirty(nameString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/otp_code_test.dart b/todo/packages/form_inputs/test/otp_code_test.dart new file mode 100644 index 0000000..9acebae --- /dev/null +++ b/todo/packages/form_inputs/test/otp_code_test.dart @@ -0,0 +1,44 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const otpString = '123456'; + group('Otp', () { + group('constructors', () { + test('pure creates correct instance', () { + const otp = Otp.pure(); + expect(otp.value, ''); + expect(otp.isPure, true); + }); + + test('dirty creates correct instance', () { + const otp = Otp.dirty(otpString); + expect(otp.value, otpString); + expect(otp.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when otp is empty', () { + expect( + const Otp.dirty().error, + OtpValidationError.invalid, + ); + }); + + test('returns invalid error when otp is malformed', () { + expect( + const Otp.dirty('test').error, + OtpValidationError.invalid, + ); + }); + + test('is valid when otp is valid', () { + expect( + const Otp.dirty(otpString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/password_test.dart b/todo/packages/form_inputs/test/password_test.dart new file mode 100644 index 0000000..46f1fc9 --- /dev/null +++ b/todo/packages/form_inputs/test/password_test.dart @@ -0,0 +1,45 @@ +// ignore_for_file: prefer_const_constructors +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const passwordString = '12345678Aa!'; + group('Password', () { + group('constructors', () { + test('pure creates correct instance', () { + final email = Password.pure(); + expect(email.value, ''); + expect(email.isPure, true); + }); + + test('dirty creates correct instance', () { + final email = Password.dirty(passwordString); + expect(email.value, passwordString); + expect(email.isPure, false); + }); + }); + + group('validator', () { + test('returns invalid error when password is empty', () { + expect( + Password.dirty().error, + PasswordValidationError.empty, + ); + }); + + test('returns invalid error when password is less than 6 characters', () { + expect( + Password.dirty('test').error, + PasswordValidationError.notLongEnough, + ); + }); + + test('is valid when password is valid', () { + expect( + Password.dirty(passwordString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/phone_number_test.dart b/todo/packages/form_inputs/test/phone_number_test.dart new file mode 100644 index 0000000..087254c --- /dev/null +++ b/todo/packages/form_inputs/test/phone_number_test.dart @@ -0,0 +1,44 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const phoneString = '+16505551234'; + group('Phone Number', () { + group('constructors', () { + test('pure creates correct instance', () { + const phone = PhoneNumber.pure(); + expect(phone.value, ''); + expect(phone.isPure, true); + }); + + test('dirty creates correct instance', () { + const phone = PhoneNumber.dirty(phoneString); + expect(phone.value, phoneString); + expect(phone.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when phone is empty', () { + expect( + const PhoneNumber.dirty().error, + PhoneNumberValidationError.empty, + ); + }); + + test('returns invalid error when phone is malformed', () { + expect( + const PhoneNumber.dirty('test').error, + PhoneNumberValidationError.invalid, + ); + }); + + test('is valid when phone is valid', () { + expect( + const PhoneNumber.dirty(phoneString).error, + PhoneNumberValidationError.invalid, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/username_test.dart b/todo/packages/form_inputs/test/username_test.dart new file mode 100644 index 0000000..ea13414 --- /dev/null +++ b/todo/packages/form_inputs/test/username_test.dart @@ -0,0 +1,44 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const userNameString = 'username'; + group('Username', () { + group('constructors', () { + test('pure creates correct instance', () { + const userName = Username.pure(); + expect(userName.value, ''); + expect(userName.isPure, true); + }); + + test('dirty creates correct instance', () { + const userName = Username.dirty(userNameString); + expect(userName.value, userNameString); + expect(userName.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when userName is empty', () { + expect( + const Username.dirty().error, + UsernameValidationError.empty, + ); + }); + + test('returns invalid error when userName is malformed', () { + expect( + const Username.dirty('test').error, + isNull, + ); + }); + + test('is valid when userName is valid', () { + expect( + const Username.dirty(userNameString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/form_inputs/test/zipcode_test.dart b/todo/packages/form_inputs/test/zipcode_test.dart new file mode 100644 index 0000000..057712f --- /dev/null +++ b/todo/packages/form_inputs/test/zipcode_test.dart @@ -0,0 +1,45 @@ +import 'package:form_inputs/form_inputs.dart'; +import 'package:test/test.dart'; + +void main() { + const zipString = '12345'; + + group('Zip Code', () { + group('constructors', () { + test('pure creates correct instance', () { + const zipcode = ZipCode.pure(); + expect(zipcode.value, ''); + expect(zipcode.isPure, true); + }); + + test('dirty creates correct instance', () { + const zipcode = ZipCode.dirty(value: zipString); + expect(zipcode.value, zipString); + expect(zipcode.isPure, false); + }); + }); + + group('validator', () { + test('returns empty error when zipcode is empty', () { + expect( + const ZipCode.dirty().error, + ZipCodeValidationError.empty, + ); + }); + + test('returns invalid error when zipcode is malformed', () { + expect( + const ZipCode.dirty(value: 'test').error, + ZipCodeValidationError.invalid, + ); + }); + + test('is valid when zipcode is valid', () { + expect( + const ZipCode.dirty(value: zipString).error, + isNull, + ); + }); + }); + }); +} diff --git a/todo/packages/todo_ui/.gitignore b/todo/packages/todo_ui/.gitignore new file mode 100644 index 0000000..dfb7f59 --- /dev/null +++ b/todo/packages/todo_ui/.gitignore @@ -0,0 +1,5 @@ +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ \ No newline at end of file diff --git a/todo/packages/todo_ui/README.md b/todo/packages/todo_ui/README.md new file mode 100644 index 0000000..a35c701 --- /dev/null +++ b/todo/packages/todo_ui/README.md @@ -0,0 +1,121 @@ +## Todo UI package + +Todo UI package containing a general design system. + +#### Output 📦 + +``` +├── todo_ui +│ ├── assets +│ │ ├── fonts +│ │ ├── icons +│ │ ├── images +│ ├── gallery +│ ├── lib +│ │ └── src +│ │ ├── colors +│ │ ├── icons +│ │ │ ├── todo_icons_dark.dart +│ │ │ ├── todo_icons_light.dart +│ │ │ └── todo_icons.dart +│ │ ├── spacing +│ │ ├── theme +│ │ ├── typography +│ │ └── widgets +│ │ ├── todo_app_bar.dart +│ │ ├── todo_outlined_button.dart +│ │ └── todo_text_field.dart +│ ├── todo_ui.dart +│ ├── .gitignore +│ ├── analysis_options.yaml +│ └── pubspec.yaml +└── ... +``` + +#### Colors + +The colors and their names come from Material 3 flutter. They should be modified to meet the project colors requirements. + +#### Spacing + +The spacings and their names are general examples, the user can add other necessary spacing. + +#### Theme + +A theme is a collection of attributes that's applied to an entire project not just an individual view. When you apply the theme, every view in the project applies each of the theme's attributes that it supports. The theme here may be dark (black background) or light (white background). + +#### Typography + +In this project, the only fonts assets being used in this case are `NotoSansDisplay-Regular` and `NotoSansDisplay-Semibold`. + +#### Widgets + +In the output of the brick, there are a textfield, an outline button and an appbar. All of which extend an example. The developer can delete these and add specific project widgets extending the widgets that flutter already provides or that they reflect the identity of the project. + +#### Adding new assets + +Everytime the user adds a new asset (such as icons, images, fonts, gifs) he must: + +```dart + cd todo_ui + flutter pub get + flutter pub run build_runner build +``` + +The `assets.gen.dart`, `fonts.gen.dart` will update. + +#### Assets usage Example + +The correct way to use the assets, such as icons, gifs, pictures after running `flutter pub run build_runner build` inside the package directory is: + +1) Inside `todo_icons.dart`: + + +```dart + SvgPicture backIcon({double? size, Color? color}) { + return Assets.icons.backIcon.svg( + color: color, + height: size ?? 14, + width: size ?? 14, + ); + } +``` +2) In `todo_icons_light.dart` add the icon with its color respectively of the theme. + + +```dart +@override + SvgPicture backIcon({double? size, Color? color}) { + return super.backIcon( + color: color ?? ProjectNameColors.black, + size: size ?? 14, + ); + } +``` + +3) In `todo_icons_dark.dart` add the icon with its color respectively of the theme. + + +```dart +@override + SvgPicture backIcon({double? size, Color? color}) { + return super.backIcon( + color: color ?? AppColors.white, + size: size ?? 14, + ); + } +``` + +4) Set the size and color of the icon when using it in the project UI: + +```dart +Theme.of(context).icons.backIcon( + size: 30, + ), +``` + +#### Gallery + +The gallery shows different visual examples of some items from the ui package as well as some to use the project theme properly. + +The theme can be changed in the gallery by clicking on the icon of the moon/sun at the top. The idea is that the developer can see the difference in colors, buttons and other widgets according to the theme chosen between dark and light. \ No newline at end of file diff --git a/todo/packages/todo_ui/analysis_options.yaml b/todo/packages/todo_ui/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/todo_ui/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf b/todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7201de3963088ee609214f688ca5a126b0e3ea7 GIT binary patch literal 113460 zcmbq+2Ylm4mA~dE+j5m9%j&&LmMmG_wk*lL_bpzp%l4K{ve{(QNFf!{XrYCWP71wq z^d8^{B>@6QfFlVI?no|22$1YS{PX|&<|oA_DaAw;`jfOv56}WAK0_scK2`pAY(g*(Uv}phWaT(liOW>xPXV7D*T`g= z*>jKW+jHOnRZ_+#Z<5JszINW8%Py#F78>yRF#0>r-*eu9)D7=BBxC0#@%LLUICjyA zD|cRWjf`FL4VkP(EfZwGi~UM?3^*xdvV_{H_Bz$h9qc;s zvM=*~jWSurElERIR?hX>Y+jGU0iwuq>|fbX z=?NOSTkgc`eVVj})oL6bY?c;m+_^Dv@#Hl~C}Pwh|xN zw=LJa#W&!)-rMiFZ@Hd*_P)NK;9uW;y-(s_?|t`Sj2)}r zWq%Z2m3d@g87Pv8Fn`L%^r?tEl}u++2`xS-(5STu7@0!r&g5Ati3YinX`AuK|4!|( zI}Wr(2g2IDTe{*~+-Cc*FTW+UXQ8Vr71x9VU9o6Wizmj^joVZP_L$PylJSggt=uuk zw2h+;8u|A%p+bCcuJ+hht3ocL(@@9k6)jGa*H|l)RmxgdUlIN&JSnS}8Dth&i!6;< zE)qEcMACX!m9|-zh^JFYpO?9rCT;^>n1a*(`h~&a{?XRFqi@*laJcc0y;ZzzjGgF+ zbUO#6Ke!wYm&fH2p1k;+b1qFE7(D0V{LJ+9?99ycOmZcSiTSV8zMF;`AOmDpkh>(#XNBg75Y5OB$P~{cl(uc2_v*b1fZ=^f}DhQM$QL-t{4Ebjn7YX ze%fHK-Jy(nZn=0$7t8ecdUq$?-Qi}he%a@U8V5Q$w_4xli@TlaXju69%t&E}=S%jP zX!rTSw{F$C4a(LXg~Vv5*^tb6MrXxqw4tOY)7xSdgs|U}^SW{YPejIu2W9W$JSZa$ zB<_lwX5ukb7U8GlCaXKk9#HhlDgEmOnW&ik<(c>{1UFo$I&tx_D!kk2HCUu*SjT?7zT0DjCEB zYgP&+UP*wND?#~0Tv~K)my)snGN!h7jqhs<@0}Un7ZdL=cg12|X6Yw8ICs;o_>PAz zI`QD#+=D0fUf0uo{ocLT_Vip!7|-Cx3?A7alR4dLnpq`lWd2No&1_==Q<|EFB3~G0 zf3*hO8kJs^&%TGg)ruc4U;pSbg(nd!_$^QoOJEqhz%CqrAh3f=9a-tKl!XUq^X z6ee4b92VSdgW;Nr+8smrJ!!RWR%aaRZOxd*uoRLVjpnAVW>@omng=_t6x6?Oy>HRa?`ltO}4rh1TfsqF=;ukUUD(s)k znR05`)vQUpgMCN*i|~f&{*^aiElE88BzCo^EVoV}1#L4l`6RxJ!ZOd~Br2sP5M@e1 zLn0IMh_fQA&n_h09qwR%AhWM;vA}H zKx*J4X(2vHA!rKaa!9?IOgdFIu_t^sn~&5IL1u+a{Gw2T#HFGXmYzvMi-ZrmhPUsiJe~p1|EpWPJm9ki3eI)%ByW)?0K=DeMP*F?LP5%{DsL+ z--Bl+Wlym`u>Sx*K-bcMQ!4RFvQLWB?33)ip`q^$4Q+�kBGKWrXb#?+38{Z{7Ww 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�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 + + diff --git a/todo/packages/todo_ui/assets/icons/email_outline.svg b/todo/packages/todo_ui/assets/icons/email_outline.svg new file mode 100644 index 0000000..cb905d5 --- /dev/null +++ b/todo/packages/todo_ui/assets/icons/email_outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/todo/packages/todo_ui/assets/images/icon.png b/todo/packages/todo_ui/assets/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/.gitignore b/todo/packages/todo_ui/gallery/.gitignore new file mode 100644 index 0000000..9d532b1 --- /dev/null +++ b/todo/packages/todo_ui/gallery/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/todo/packages/todo_ui/gallery/.metadata b/todo/packages/todo_ui/gallery/.metadata new file mode 100644 index 0000000..182ccca --- /dev/null +++ b/todo/packages/todo_ui/gallery/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 78910062997c3a836feee883712c241a5fd22983 + channel: stable + +project_type: app diff --git a/todo/packages/todo_ui/gallery/README.md b/todo/packages/todo_ui/gallery/README.md new file mode 100644 index 0000000..84fc389 --- /dev/null +++ b/todo/packages/todo_ui/gallery/README.md @@ -0,0 +1,9 @@ +## Gallery + +Gallery project to showcase todo_ui + +To look through the galleryApp its only necessary to open `main.dart` and then run. + +The developer can look through the gallery with darkTheme or lightTheme by clicking on the icon of the moon/sun at the top. + + diff --git a/todo/packages/todo_ui/gallery/analysis_options.yaml b/todo/packages/todo_ui/gallery/analysis_options.yaml new file mode 100644 index 0000000..2773f5a --- /dev/null +++ b/todo/packages/todo_ui/gallery/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:very_good_analysis/analysis_options.5.1.0.yaml + +linter: + rules: + public_member_api_docs: false diff --git a/todo/packages/todo_ui/gallery/android/.gitignore b/todo/packages/todo_ui/gallery/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/todo/packages/todo_ui/gallery/android/app/build.gradle b/todo/packages/todo_ui/gallery/android/app/build.gradle new file mode 100644 index 0000000..18939ff --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.todo_ui.gallery" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..3fbe6ee --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8fb017c --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt b/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt new file mode 100644 index 0000000..ec3a38f --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt @@ -0,0 +1,6 @@ +package com.todo.ui.gallery + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..3db14bb --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d460d1e --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..3fbe6ee --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/todo/packages/todo_ui/gallery/android/build.gradle b/todo/packages/todo_ui/gallery/android/build.gradle new file mode 100644 index 0000000..4256f91 --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/todo/packages/todo_ui/gallery/android/gradle.properties b/todo/packages/todo_ui/gallery/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties b/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bc6a58a --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/todo/packages/todo_ui/gallery/android/settings.gradle b/todo/packages/todo_ui/gallery/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/todo/packages/todo_ui/gallery/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/todo/packages/todo_ui/gallery/ios/.gitignore b/todo/packages/todo_ui/gallery/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist b/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..8d4492f --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig b/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig b/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/todo/packages/todo_ui/gallery/ios/Podfile b/todo/packages/todo_ui/gallery/ios/Podfile new file mode 100644 index 0000000..1e8c3c9 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..08086b8 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift b/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Info.plist b/todo/packages/todo_ui/gallery/ios/Runner/Info.plist new file mode 100644 index 0000000..0a43b23 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Gallery + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + gallery + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h b/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/todo/packages/todo_ui/gallery/lib/colors/colors.dart b/todo/packages/todo_ui/gallery/lib/colors/colors.dart new file mode 100644 index 0000000..0b9c7d0 --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/colors/colors.dart @@ -0,0 +1 @@ +export 'colors_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart b/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart new file mode 100644 index 0000000..a1c900e --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class ColorsPage extends StatelessWidget { + const ColorsPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const ColorsPage()); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorItems = [ + _ColorTextContainer( + color: theme.colorScheme.primary, + name: 'Primary ', + ), + _ColorTextContainer( + color: theme.colorScheme.onPrimary, + name: 'On primary ', + ), + _ColorTextContainer( + color: theme.colorScheme.primaryContainer, + name: 'primary Container', + ), + _ColorTextContainer( + color: theme.colorScheme.onPrimaryContainer, + name: 'on primary container', + ), + _ColorTextContainer( + color: theme.colorScheme.secondary, + name: 'Secondary ', + ), + _ColorTextContainer( + color: theme.colorScheme.onSecondary, + name: 'On Secondary ', + ), + _ColorTextContainer( + color: theme.colorScheme.secondaryContainer, + name: 'Secondary container', + ), + _ColorTextContainer( + color: theme.colorScheme.onSecondaryContainer, + name: 'On secondary container', + ), + _ColorTextContainer( + color: theme.colorScheme.tertiary, + name: 'Tertiary ', + ), + _ColorTextContainer( + color: theme.colorScheme.onTertiary, + name: 'On tertiary ', + ), + _ColorTextContainer( + color: theme.colorScheme.tertiaryContainer, + name: 'Tertiary container ', + ), + _ColorTextContainer( + color: theme.colorScheme.onTertiaryContainer, + name: 'On tertiary container ', + ), + _ColorTextContainer( + color: theme.colorScheme.error, + name: 'Error ', + ), + _ColorTextContainer( + color: theme.colorScheme.onError, + name: 'On error ', + ), + _ColorTextContainer( + color: theme.colorScheme.errorContainer, + name: 'Error container', + ), + _ColorTextContainer( + color: theme.colorScheme.onErrorContainer, + name: 'on error container', + ), + _ColorTextContainer( + color: theme.colorScheme.surface, + name: 'Background', + textColor: theme.colorScheme.onSurface, + ), + _ColorTextContainer( + color: theme.colorScheme.onSurface, + name: 'On background', + textColor: theme.colorScheme.surface, + ), + _ColorTextContainer( + color: theme.colorScheme.surface, + name: 'Surface', + textColor: theme.colorScheme.onSurface, + ), + _ColorTextContainer( + color: theme.colorScheme.onSurface, + name: 'On surface', + textColor: theme.colorScheme.surface, + ), + _ColorTextContainer( + color: theme.colorScheme.surfaceContainerHighest, + name: 'Sureface Variant', + textColor: UIColors.black, + ), + _ColorTextContainer( + color: theme.colorScheme.onSurfaceVariant, + name: 'On Sureface Variant', + ), + _ColorTextContainer( + color: theme.colorScheme.inversePrimary, + name: 'Inverse primary', + ), + ]; + + return Scaffold( + appBar: AppBar( + title: Text( + 'ColorTheme based on Material Flutter'.toUpperCase(), + ), + titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: colorItems.map((colorContainer) { + return colorContainer; + }).toList(), + ), + ), + ); + } +} + +class _ColorTextContainer extends StatelessWidget { + const _ColorTextContainer({ + required this.color, + required this.name, + this.textColor, + }); + + final Color color; + final Color? textColor; + final String name; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all( + 10, + ), + margin: const EdgeInsets.symmetric( + horizontal: UISpacing.lg * 2, + vertical: UISpacing.xs, + ), + width: UISpacing.spaceUnit*100, + height: UISpacing.xxlg, + decoration: BoxDecoration( + color: color, + border: Border.all( + color: theme.colorScheme.onSurface, + ), // Set border width + borderRadius: const BorderRadius.all( + Radius.circular(UISpacing.xxs * 5), + ), + ), + child: Center( + child: Text( + name.toUpperCase(), + maxLines: 2, + style: theme.textTheme.labelLarge?.copyWith( + fontSize: 10, + fontWeight: FontWeight.w600, + color: textColor ?? UIColors.black, + ), + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/main.dart b/todo/packages/todo_ui/gallery/lib/main.dart new file mode 100644 index 0000000..60e8ec4 --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/main.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/colors/colors.dart'; +import 'package:gallery/spacing/spacing.dart'; +import 'package:gallery/typography/typography.dart'; +import 'package:gallery/widgets/widgets.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + bool _isDark = false; + + void _toggleTheme() { + setState(() { + _isDark = !_isDark; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Example Gallery', + theme: _isDark ? UITheme().darkTheme : UITheme().lightTheme, + home: RootPage(toggleTheme: _toggleTheme), + ); + } +} + +class RootPage extends StatelessWidget { + const RootPage({ + required this.toggleTheme, + super.key, + }); + + final VoidCallback toggleTheme; + + @override + Widget build(BuildContext context) { + final pages = [ + _ListItem( + icon: const Icon(Icons.color_lens), + title: const Text('Colors'), + subtitle: const Text('Some predefined colors'), + onTap: () => Navigator.of(context).push(ColorsPage.route()), + ), + _ListItem( + icon: const Icon(Icons.text_format), + title: const Text('Typography'), + subtitle: const Text('Some predefined text styles'), + onTap: () => Navigator.of(context).push(TypographyPage.route()), + ), + _ListItem( + icon: const Icon(Icons.border_vertical), + title: const Text('Spacing'), + subtitle: const Text('All of the predefined spacings'), + onTap: () => Navigator.of(context).push(SpacingPage.route()), + ), + _ListItem( + icon: const Icon(Icons.widgets), + title: const Text('Widgets'), + subtitle: const Text('Some predefined custom widgets'), + onTap: () => Navigator.of(context).push(WidgetsPage.route()), + ), + ]; + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + title: Text( + 'Example Theme Gallery'.toUpperCase(), + ), + titleTextStyle: theme.appBarTheme.titleTextStyle, + leading: IconButton( + onPressed: toggleTheme, + icon: Icon( + theme.brightness == Brightness.dark + ? Icons.light_mode_outlined + : Icons.dark_mode_outlined, + ), + ), + ), + body: ListView.separated( + itemCount: pages.length, + itemBuilder: (_, index) => pages[index], + separatorBuilder: (_, __) => Divider( + color: theme.dividerColor, + ), + ), + ); + } +} + +class _ListItem extends StatelessWidget { + const _ListItem({ + required this.onTap, + required this.icon, + required this.title, + required this.subtitle, + }); + + final VoidCallback onTap; + final Icon icon; + final Text title; + final Text subtitle; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return ListTile( + leading: IconTheme( + data: IconThemeData( + color: theme.iconTheme.color, + ), + child: icon, + ), + title: title, + subtitle: subtitle, + trailing: const Icon(Icons.arrow_forward), + onTap: onTap, + iconColor: theme.colorScheme.onSurface, + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart b/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart new file mode 100644 index 0000000..00763fc --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart @@ -0,0 +1 @@ +export 'spacing_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart b/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart new file mode 100644 index 0000000..476f5db --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class SpacingPage extends StatelessWidget { + const SpacingPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const SpacingPage()); + } + + @override + Widget build(BuildContext context) { + const spacingList = [ + _SpacingItem(spacing: UISpacing.xxxs, name: 'xxxs'), + _SpacingItem(spacing: UISpacing.xxs, name: 'xxs'), + _SpacingItem(spacing: UISpacing.xs, name: 'xs'), + _SpacingItem(spacing: UISpacing.sm, name: 'sm'), + _SpacingItem(spacing: UISpacing.md, name: 'md'), + _SpacingItem(spacing: UISpacing.lg, name: 'lg'), + _SpacingItem(spacing: UISpacing.xlg, name: 'xlg'), + _SpacingItem(spacing: UISpacing.xxlg, name: 'xxlg'), + _SpacingItem(spacing: UISpacing.xxxlg, name: 'xxxlg'), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('Spacing')), + body: ListView.builder( + itemCount: spacingList.length, + itemBuilder: (_, index) => spacingList[index], + ), + ); + } +} + +class _SpacingItem extends StatelessWidget { + const _SpacingItem({required this.spacing, required this.name}); + + final double spacing; + final String name; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(UISpacing.sm), + child: Row( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + color: UIColors.black, + width: UISpacing.xxs, + height: UISpacing.lg, + ), + Container( + width: spacing, + height: UISpacing.lg, + color: UIColors.skyBlue, + ), + Container( + color: UIColors.black, + width: UISpacing.xxs, + height: UISpacing.lg, + ), + ], + ), + const SizedBox(width: UISpacing.sm), + Text(name), + ], + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/typography/typography.dart b/todo/packages/todo_ui/gallery/lib/typography/typography.dart new file mode 100644 index 0000000..770dff3 --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/typography/typography.dart @@ -0,0 +1 @@ +export 'typography_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart b/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart new file mode 100644 index 0000000..59281a9 --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class TypographyPage extends StatelessWidget { + const TypographyPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const TypographyPage()); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final uiTextStyleList = [ + _TextItem(name: 'Display Large', style: theme.textTheme.displayLarge!), + _TextItem(name: 'Display Medium', style: theme.textTheme.displayMedium!), + _TextItem(name: 'Display Small', style: theme.textTheme.displaySmall!), + _TextItem( + name: 'Headline Medium', + style: theme.textTheme.headlineMedium!, + ), + _TextItem(name: 'Headline Small', style: theme.textTheme.headlineSmall!), + _TextItem(name: 'Title large', style: theme.textTheme.titleLarge!), + _TextItem(name: 'Title medium', style: theme.textTheme.titleMedium!), + _TextItem(name: 'Title small', style: theme.textTheme.titleSmall!), + _TextItem(name: 'Body large', style: theme.textTheme.bodyLarge!), + _TextItem(name: 'Body medium', style: theme.textTheme.bodyMedium!), + _TextItem(name: 'Body small', style: theme.textTheme.bodySmall!), + _TextItem(name: 'Label large', style: theme.textTheme.labelLarge!), + _TextItem(name: 'Label Small', style: theme.textTheme.labelSmall!), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('Typography')), + body: ListView( + children: [ + const Center(child: Text('UI Typography Text styles')), + const SizedBox(height: 16), + ...uiTextStyleList, + ], + ), + ); + } +} + +class _TextItem extends StatelessWidget { + const _TextItem({required this.name, required this.style}); + + final String name; + final TextStyle style; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: UISpacing.sm, + vertical: UISpacing.lg, + ), + child: Text(name, style: style), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart new file mode 100644 index 0000000..c9ff46c --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class AppBarPage extends StatefulWidget { + const AppBarPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const AppBarPage()); + } + + @override + State createState() => _AppBarPage(); +} + +class _AppBarPage extends State { + @override + Widget build(BuildContext context) { + const contentSpacing = UISpacing.sm; + final appBarList = [ + const SizedBox(height: contentSpacing), + UIAppBar.redAppBar( + textTitle: 'Red AppBar', + ), + const SizedBox(height: contentSpacing), + UIAppBar.actionAppBar( + textTitle: 'Action AppBar', + ), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('App Assets')), + body: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: appBarList.map((appBar) { + return appBar; + }).toList(), + ), + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart new file mode 100644 index 0000000..afa2f74 --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class ButtonPage extends StatelessWidget { + const ButtonPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const ButtonPage()); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + const contentSpacing = UISpacing.sm; + final appButtonList = [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Icon button'), + IconButton( + onPressed: () {}, + color: theme.colorScheme.primary, + icon: const Icon(Icons.add), + ), + ], + ), + const SizedBox(height: contentSpacing), + OutlinedButton( + onPressed: () {}, + style: theme.outlinedButtonTheme.style, + child: Text( + 'Secondary button', + style: Theme.of(context).textTheme.labelLarge, + ), + ), + const SizedBox(height: contentSpacing), + OutlinedButton( + onPressed: () {}, + style: theme.outlinedButtonTheme.style, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + theme.icons.emailOutline( + color: UIColors.blue, + ), + const SizedBox(width: contentSpacing), + const Text('Primary button + logo'), + ], + ), + ), + const SizedBox(height: contentSpacing), + TextButton( + style: Theme.of(context).textButtonTheme.style, + onPressed: () {}, + child: const Text( + 'Text button', + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: contentSpacing), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Floating action button'), + const SizedBox( + width: 10, + ), + FloatingActionButton( + onPressed: () {}, + backgroundColor: theme.colorScheme.surface, + child: theme.icons.emailOutline(size: 20), + ), + ], + ), + const SizedBox(height: contentSpacing), + Chip( + backgroundColor: theme.chipTheme.backgroundColor, + padding: EdgeInsets.zero, + label: const Padding( + padding: EdgeInsets.symmetric( + vertical: UISpacing.xxs, + horizontal: UISpacing.xs, + ), + child: Text( + 'Common chip', + ), + ), + ), + const SizedBox(height: contentSpacing), + ChoiceChip( + avatar: theme.icons.emailOutline( + color: UIColors.black, + ), + padding: const EdgeInsets.all(8), + label: Text( + 'Icon chip', + style: theme.chipTheme.labelStyle, + ), + selected: false, + ), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('App Buttons & Chips')), + body: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: appButtonList.map((button) { + return button; + }).toList(), + ), + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart new file mode 100644 index 0000000..59fddfc --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class IconsPage extends StatefulWidget { + const IconsPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const IconsPage()); + } + + @override + State createState() => _IconsPage(); +} + +class _IconsPage extends State { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + const contentSpacing = UISpacing.sm; + final appIconsList = [ + const SizedBox(height: contentSpacing), + theme.icons.emailOutline( + size: 30, + ), + const SizedBox(height: contentSpacing), + theme.icons.backIcon( + size: 30, + ), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('App Assets')), + body: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: appIconsList.map((icon) { + return icon; + }).toList(), + ), + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart new file mode 100644 index 0000000..e5d79fe --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class TextFieldPage extends StatelessWidget { + const TextFieldPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const TextFieldPage()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + 'Text Field', + ), + ), + body: Padding( + padding: const EdgeInsets.all(UISpacing.lg), + child: Column( + children: [ + UITextField.emailTextField( + onChanged: (_) {}, + ), + const SizedBox(height: UISpacing.sm), + UITextField.passwordTextField( + onChanged: (_) {}, + ), + const SizedBox(height: UISpacing.sm), + UITextField( + hintText: 'Default text field', + onChanged: (_) {}, + ), + ], + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart b/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart new file mode 100644 index 0000000..35b87fc --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'app_bar_page.dart'; +export 'button_page.dart'; +export 'icons_page.dart'; +export 'text_field_page.dart'; +export 'widgets_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart new file mode 100644 index 0000000..3e8debb --- /dev/null +++ b/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/widgets/widgets.dart'; + +class WidgetsPage extends StatelessWidget { + const WidgetsPage({super.key}); + + static Route route() { + return MaterialPageRoute(builder: (_) => const WidgetsPage()); + } + + @override + Widget build(BuildContext context) { + final widgets = <_ListItem>[ + _ListItem( + icon: const Icon(Icons.vignette), + title: const Text('App Buttons'), + onTap: () => Navigator.of(context).push(ButtonPage.route()), + ), + _ListItem( + icon: const Icon(Icons.email_outlined), + title: const Text('Text Fields'), + onTap: () => Navigator.of(context).push(TextFieldPage.route()), + ), + _ListItem( + icon: const Icon(Icons.drag_indicator_outlined), + title: const Text('Icons'), + onTap: () => Navigator.of(context).push(IconsPage.route()), + ), + _ListItem( + icon: const Icon(Icons.drag_indicator_outlined), + title: const Text('Appbar'), + onTap: () => Navigator.of(context).push(AppBarPage.route()), + ), + ]; + + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar(title: const Text('Widgets')), + body: ListView.separated( + itemCount: widgets.length, + itemBuilder: (_, index) => widgets[index], + separatorBuilder: (_, __) => Divider( + color: theme.dividerColor, + ), + ), + ); + } +} + +class _ListItem extends StatelessWidget { + const _ListItem({ + required this.icon, + required this.title, + this.onTap, + }); + + final VoidCallback? onTap; + final Widget icon; + final Text title; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return ListTile( + leading: IconTheme( + data: IconThemeData( + color: theme.iconTheme.color, + ), + child: icon, + ), + title: title, + trailing: Icon( + Icons.chevron_right, + color: theme.colorScheme.onSurface, + ), + onTap: onTap, + iconColor: theme.iconTheme.color, + ); + } +} diff --git a/todo/packages/todo_ui/gallery/pubspec.yaml b/todo/packages/todo_ui/gallery/pubspec.yaml new file mode 100644 index 0000000..23cc13f --- /dev/null +++ b/todo/packages/todo_ui/gallery/pubspec.yaml @@ -0,0 +1,20 @@ +name: gallery +description: Gallery project to showcase todo_ui +version: 0.0.1 +publish_to: none + +environment: + sdk: ">=2.18.0 <3.0.0" + +dependencies: + todo_ui: + path: ../ + flutter: + sdk: flutter + flutter_asset_generator: ^1.0.2 + +dev_dependencies: + very_good_analysis: ^5.1.0 + +flutter: + uses-material-design: true diff --git a/todo/packages/todo_ui/gallery/web/favicon.png b/todo/packages/todo_ui/gallery/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/web/icons/Icon-192.png b/todo/packages/todo_ui/gallery/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..510b28316f0e86cac194f79338c2f7e0eafaa101 GIT binary patch literal 840 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX1GXs1=T>t<7A9C~uWB6>%ZQu4k z`SarIzfV8^)t~=)^Zv`PKYvfU`YZR;&rqO((CPXq3+{dRn{eWX(T;Ctp8xSa_?fkNvpy?k}THhvTkq+S|V|*yXFOd1;)w&>&?2qh}L?UNEC;wc5JZTmi*5 z-~O@N`Auj0H=g)O+DY>m%#*kyni;gb-u?J@_Qjv1Bi|g>zckF5tsObx!M^3%3=B+J zo-U3d6}R48a^yM`z~g#R{|48qh`TP|_uD#$Pk5+pnJ%}ZWjoL_3=BXK7ioknxOzDf|9EJY=4Gv(sUn>{r*l!h5cNy=`k#^m}nb zh5g2cpMU*X@BGtvaMwTjz@LB1neysIKD=A5n^5;Zm~p#3SH<_sVjTDX|J1ztqMvEu z|BPu_U)&FVsC!^9l^fjqPh-L!`x#56{st<1c5ig67vXu&p5Q9=cXyLw{T+wJ_FO6d zG%md8Z+iH5i}od;{+af93ohQ@S-#Mo%jB2!M*FZMZ~LVSfDXAWul?xTao!z&0}s6I z7cKn#m<{NpfLH$+>p5@xPyKf;zlCx2`;Eb!{6_y{_k~9Vu+IEn@@dhQ1NkZcJd4(E zRXFkIt5`_l347~b%%?=xmZe^karn$zBk^Ir$|-KWbs2RL&kP%iT;2a0IUi8DSjQ}X z?w5F~9P<_NkcV^rZ{mIseX%C?c!MU>m-`3W>;H8x5_8%RfBTT;cS5|BC66??28AyYfT4GW&cfv;2ej>Akk8j$cbPHto~6 z+jY$TV!Z literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/web/icons/Icon-512.png b/todo/packages/todo_ui/gallery/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2fca6c86d486e344a26c045e7a34eae28097ff GIT binary patch literal 2172 zcmd^>ZBSEJ8pqGQ2_%q}grY#JA-HN4CDn!0yp=ZUfNHc&a~ax{nlsB zoag!ff6p^>=FItUj>N@8E^~gu833?sbJV8o0N`wi13rgM?Ps6tVpDNkbbJItDE>(*46Kg= z{)v)`9&yPPud?>P4blH<={<1%mI~Eq0N~Ya-V_#}JFNfueJ64?0AYpr8OEBy4=bXQ zCLGx#|5|9yEldXlbsbi>R2@>fpbU7|-nfu8JAR+)B~GD$|Eqz%h~zC@Dk!jD;59El zIE(J1UUUitqAWOWrxu9ck#$iBEm!sM%CZfocTC4K+8z|ibNIVd9R7`V_SDM ze6V9^qFM}p++HY0bdT1zhOFu$9zVT*N&;;I6XU^lle(RH$97LqOz4cAlnc5wQBH_q zWxE7DZ%@ie2)It=n799#uugG|2E~2=okl|eD$Nv=#Fhb}Mpp9{Pg{Klag2Ra2FzA7 zPXqhx;(Gudxxo5ER@SnxL(O?!B$2%V4t3koeC}0OmJ$bkS?pmHeOX&qz$Eb`DTX>T zus3ehV2A8I93xfpkf%3Eg&HEp?1xyA<}EZY$z-$W2lp08O&gM>a@>0JP98 zoM&ys@7}`&Ml(!zp4BD9HGW$6P}k9HWWg^9QNnlq``vrlGw zD6b}81J4EvQjqm_&$0F034o?r4q}Pf?i$$nrJ(;WUjU23C7C>4lo*6K{erj~4y6WS zcu~zU;_tqV71CKkRR%~V>w@|o5fakF*jY6?9pKDK^*nxfL)r?J>t3hC)k7Boz8ORF z|2|n!umWzc?I@+J?yZrRT_ekEX_gZLdA3OTpu{?g;wzd%Kr4`I>er3EM<5V=3VU~sRzje*p{_p8E6I*0g^ludF>vYcVqG4j;ng&+*kR?3vgFaV(!Sra-PN?@Z&@# zFS^G;i*fN)-1L&%S!f2H7{H!hJ9Y^*)L!9e1%O-oW+8d^P!6{DpL{+hC(BA2^@?pA z5_r32-6fm>H~nD}o~c_y?&Gq(M{G1ruT=3whaKf)RkFMCs*RCh-@1X-@|UH~wwzBm zDo$K6t5Q4EdStZ+f#<X~3Kvo&cgK~X z{C;=SImW;v!2>m^^_Tyr7<}Za9%8wR=5~%YFQ4E4gy}29FkVZnN zy2(!ad6@KYt*Z9B$zpKKHFic?neTmzhrPb8Y7DhRR|@gy$Pg1ezriPrG?hfQqN8n# zqLh$HC3@%O;_`gHpO+~G?SS@bAzm73Z$=H#<2Ul>mUK8kFFr1D6DJIBELyKq|raOTO)^7h*TwN(_{6J%#x0-?l;rKM%)2iYJ+x<}<6# z@qOvj^r%W~zvG?9(1WN6Yozw|aB4|gt9tBL#rw@yo0rEHFBBIQ zKk(cGL1qYN+ZMs8p7A@~0lh4HcHtL~?!cp^lsk>#Ldv;EVeY*^vM>BuvU61UFKHHq zUT^%L+UyhLCa4yaZntgl{#vm&>M}we4~qIXrCF}4TomE?WNW&Qg+tKxilEijJ>MSm z{5!sp*dz6rPcHp_n*DTfBU640N{0D7lN?~tEg7fS<7~v-4Hwg|E;2)UeWV)UZKyz^ zGw18oc~Aj-)-07Qw5lD41zzuaHkNmj?e>f&kYFq3o5mtkTHd>^;` zwIDJDq1V~i_vGtGcq)m(b4fbcKy!nPJP#*({Tv&L69$=S`VAzOk}d=TcJdPxbGia^?Z=qCm{xt_Ps%kJ%w=^6S14NQ z$vHID3F4%w!=#Uu#`u5|+R6Nhj5Dny_c9bVhFHh8nN9`CHFc9zGh<9eAo0FB+e$>SfTB_K!3^le?sp8xy;T`;$sE={oEDBO|Y4?y961HPTwy zETAh$%S=`X9wuE{XNOWM6M8}PhRh=8NDaMhrk9*jek08k9hl62wY*fBc2%w*GcsAI zc-pev)R=t~AMy8RC)0_T3Y*z>S9?>85u^yXxR8xb5QO9W@;i>XN$9#d25ox~&sqHD^j? z`L+9*fyviY{fQ4L!l`INVRZuQvO06qs%D5c?oQX}WllNAJ}xN7Q4SKJ-OD%w&ueEU iM9jZ%HFXYXyU%f`ZK8t=g|G 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 literal 0 HcmV?d00001 diff --git a/todo/packages/todo_ui/gallery/web/index.html b/todo/packages/todo_ui/gallery/web/index.html new file mode 100644 index 0000000..cfd3b6a --- /dev/null +++ b/todo/packages/todo_ui/gallery/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + gallery + + + + + + + diff --git a/todo/packages/todo_ui/gallery/web/manifest.json b/todo/packages/todo_ui/gallery/web/manifest.json new file mode 100644 index 0000000..cf706d7 --- /dev/null +++ b/todo/packages/todo_ui/gallery/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "gallery", + "short_name": "gallery", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/todo/packages/todo_ui/lib/src/colors/todo_colors.dart b/todo/packages/todo_ui/lib/src/colors/todo_colors.dart new file mode 100644 index 0000000..2515e12 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/colors/todo_colors.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; + +/// A class which contains all the colors used in the app. +abstract class UIColors { + /// Black + static const Color black = Color(0xFF000000); + + /// White + static const Color white = Color(0xFFFFFFFF); + + /// Transparent + static const Color transparent = Color(0x00000000); + + /// The grey primary color and swatch. + static const Color grey = Colors.grey; + + /// The light grey color. + static const Color lightGrey = Color(0xFFEEEEEE); + + /// The liver color. + static const Color liver = Color(0xFF4D4D4D); + + /// The dark aqua color. + static const Color darkAqua = Color(0xFF00677F); + + /// The blue primary color and swatch. + static const Color blue = Color(0xFF3898EC); + + /// The sky blue color. + static const Color skyBlue = Color(0xFF0175C2); + + /// The ocean blue color. + static const Color oceanBlue = Color(0xFF02569B); + + /// The light blue accent color. + static const Color lightBlue = Color(0xFFB3E5FC); + + /// The light blue accent color. + static const Color lightBlue200 = Color(0xFFB3E5FC); + + /// The crystal blue color. + static const Color crystalBlue = Color(0xFF55ACEE); + + /// The light surface dress color. + static const Color surface = Color(0xFFEBF2F7); + + /// The pale sky color. + static const Color paleSky = Color(0xFF73777F); + + /// The bright grey color. + static const Color brightGrey = Color(0xFFEAEAEA); + + /// The red primary color and swatch. + static const MaterialColor red = Colors.red; + + /// The on-background color. + static const Color onBackground = Color(0xFF1A1A1A); + + /// The primary container color. + static const Color primaryContainer = Color(0xFFB1EBFF); + + /// The red wine color. + static const Color redWine = Color(0xFF9A031E); + + /// The modal background color. + static const Color modalBackground = Color(0xFFEBF2F7); + + /// The medium emphasis primary color. + static const Color mediumEmphasisPrimary = Color(0xBDFFFFFF); + + /// The medium emphasis surface color. + static const Color mediumEmphasisSurface = Color(0x99000000); + + /// The border outline color. + static const Color borderOutline = Color(0x33000000); + + /// The light outline color. + static const Color outlineLight = Color(0x33000000); + + /// The secondary color of application. + static const MaterialColor secondary = MaterialColor(0xFF963F6E, { + 50: Color(0xFFFFECF3), + 100: Color(0xFFFFD8E9), + 200: Color(0xFFFFAFD6), + 300: Color(0xFFF28ABE), + 400: Color(0xFFD371A3), + 500: Color(0xFFB55788), + 600: Color(0xFF963F6E), + 700: Color(0xFF7A2756), + 800: Color(0xFF5F0F40), + 900: Color(0xFF3D0026), + }); +} diff --git a/todo/packages/todo_ui/lib/src/extensions/extensions.dart b/todo/packages/todo_ui/lib/src/extensions/extensions.dart new file mode 100644 index 0000000..58d9d63 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/extensions/extensions.dart @@ -0,0 +1 @@ +export 'theme_data.dart'; diff --git a/todo/packages/todo_ui/lib/src/extensions/theme_data.dart b/todo/packages/todo_ui/lib/src/extensions/theme_data.dart new file mode 100644 index 0000000..2f45291 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/extensions/theme_data.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +///Extension on Themedata that must be added to return the correct asset +extension ThemeX on ThemeData { + ///Returns the correct UIIcon based on the current theme + UIIcon get icons { + final isDarkMode = brightness == Brightness.dark; + if (isDarkMode) { + return UIIconsDark(); + } else { + return UIIconsLight(); + } + } +} diff --git a/todo/packages/todo_ui/lib/src/icons/icons.dart b/todo/packages/todo_ui/lib/src/icons/icons.dart new file mode 100644 index 0000000..cfe92d4 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/icons/icons.dart @@ -0,0 +1,3 @@ +export 'todo_icons.dart'; +export 'todo_icons_dark.dart'; +export 'todo_icons_light.dart'; diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons.dart new file mode 100644 index 0000000..d21001e --- /dev/null +++ b/todo/packages/todo_ui/lib/src/icons/todo_icons.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:todo_ui/gen/assets.gen.dart'; + +/// A class which contains all the icons used in the app +abstract class UIIcon { + final _package = 'todo_ui'; + + ///Returns the back icon + SvgPicture backIcon({double? size, Color? color}) { + return Assets.icons.backIcon.svg( + height: size ?? 14, + width: size ?? 14, + package: _package, + ); + } + + ///Returns the email outline iconc + SvgPicture emailOutline({double? size, Color? color}) { + return Assets.icons.emailOutline.svg( + height: size ?? 14, + width: size ?? 14, + package: _package, + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart new file mode 100644 index 0000000..3b5a0e0 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// Icons to be used with dark theme. +class UIIconsDark extends UIIcon { + @override + SvgPicture backIcon({double? size, Color? color}) { + return super.backIcon( + color: color ?? UIColors.white, + size: size ?? 14, + ); + } + + @override + SvgPicture emailOutline({double? size, Color? color}) { + return super.emailOutline( + color: color ?? UIColors.white, + size: size ?? 14, + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart new file mode 100644 index 0000000..e4311c3 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// Icons to be used with light theme. +class UIIconsLight extends UIIcon { + @override + SvgPicture backIcon({double? size, Color? color}) { + return super.backIcon( + color: color ?? UIColors.black, + size: size ?? 14, + ); + } + + @override + SvgPicture emailOutline({double? size, Color? color}) { + return super.emailOutline( + color: color ?? UIColors.black, + size: size ?? 14, + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart b/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart new file mode 100644 index 0000000..d1949aa --- /dev/null +++ b/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart @@ -0,0 +1,38 @@ +/// A class that contains the spacing values used in the app. +abstract class UISpacing { + /// The default unit of spacing + static const double spaceUnit = 16; + + /// xxxs spacing value (1pt) + static const double xxxs = 0.0625 * spaceUnit; + + /// xxs spacing value (2pt) + static const double xxs = 0.125 * spaceUnit; + + /// xs spacing value (4pt) + static const double xs = 0.25 * spaceUnit; + + /// sm spacing value (8pt) + static const double sm = 0.5 * spaceUnit; + + /// md spacing value (12pt) + static const double md = 0.75 * spaceUnit; + + /// lg spacing value (16pt) + static const double lg = spaceUnit; + + /// xlg spacing value (24pt) + static const double xlg = 1.5 * spaceUnit; + + /// xxlg spacing value (40pt) + static const double xxlg = 2.5 * spaceUnit; + + /// xxxlg spacing value (64pt) + static const double xxxlg = 4 * spaceUnit; + + /// xxxlg spacing value (88pt) + static const double xxxxlg = 5.5 * spaceUnit; + + /// s10 + static const double s10 = 10; +} diff --git a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart new file mode 100644 index 0000000..84d0aa3 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart @@ -0,0 +1,426 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// A class that provides the theme for the app. +class UITheme { + + /// Light Theme example based on Material 2 Design. + ThemeData get lightTheme { + return ThemeData( + primaryColor: UIColors.skyBlue, + canvasColor: _backgroundColor, + scaffoldBackgroundColor: _backgroundColor, + iconTheme: _lightIconTheme, + appBarTheme: _lightAppBarTheme, + dividerTheme: _dividerTheme, + textTheme: _lightTextTheme, + buttonTheme: _buttonTheme, + splashColor: UIColors.transparent, + elevatedButtonTheme: _elevatedButtonTheme, + textButtonTheme: _textButtonTheme, + colorScheme: _lightColorScheme, + bottomSheetTheme: _lightBottomSheetTheme, + listTileTheme: _listTileTheme, + switchTheme: _switchTheme, + progressIndicatorTheme: _progressIndicatorTheme, + tabBarTheme: _tabBarTheme, + bottomNavigationBarTheme: _bottomAppBarTheme, + chipTheme: _chipTheme, + dividerColor: UIColors.grey, + outlinedButtonTheme: _lightOutlinedButtonTheme, + ); + } + + /// Dark Theme example based on Material 2 Design. + ThemeData get darkTheme => lightTheme.copyWith( + chipTheme: _darkChipTheme, + brightness: Brightness.dark, + scaffoldBackgroundColor: UIColors.black, + colorScheme: _darkColorScheme, + appBarTheme: _darkAppBarTheme, + disabledColor: UIColors.white.withOpacity(0.5), + textTheme: _darkTextTheme, + unselectedWidgetColor: UIColors.lightGrey, + iconTheme: _darkIconTheme, + bottomSheetTheme: _darkBottomSheetTheme, + outlinedButtonTheme: _darkOutlinedButtonTheme, + textButtonTheme: _darkTextButtonTheme, + ); + + /// Returns the correct color based on the current theme. + ColorScheme get _lightColorScheme { + return ColorScheme.light( + primary: UIColors.skyBlue, + primaryContainer: UIColors.lightBlue200, + onPrimaryContainer: UIColors.oceanBlue, + secondary: UIColors.paleSky, + onSecondary: UIColors.white, + secondaryContainer: UIColors.lightBlue200, + onSecondaryContainer: UIColors.oceanBlue, + tertiary: UIColors.secondary.shade700, + onTertiary: UIColors.white, + tertiaryContainer: UIColors.secondary.shade300, + onTertiaryContainer: UIColors.secondary.shade700, + error: UIColors.red, + errorContainer: UIColors.red.shade200, + onErrorContainer: UIColors.redWine, + surface: _backgroundColor, + onSurface: UIColors.onBackground, + surfaceContainerHighest: UIColors.surface, + onSurfaceVariant: UIColors.grey, + inversePrimary: UIColors.crystalBlue, + ); + } + + /// Returns the correct color based on the current theme. + ColorScheme get _darkColorScheme => _lightColorScheme.copyWith( + surface: UIColors.black, + onSurface: UIColors.lightGrey, + primary: UIColors.blue, + onPrimary: UIColors.oceanBlue, + primaryContainer: UIColors.oceanBlue, + onPrimaryContainer: UIColors.lightBlue200, + secondary: UIColors.paleSky, + onSecondary: UIColors.lightGrey, + secondaryContainer: UIColors.liver, + onSecondaryContainer: UIColors.brightGrey, + ); + + /// Returns the correct background color based on the current theme. + Color get _backgroundColor => UIColors.white; + + /// Returns the correct [AppBarTheme] based on the current theme. + AppBarTheme get _lightAppBarTheme { + return AppBarTheme( + iconTheme: _lightIconTheme, + titleTextStyle: _lightTextTheme.titleMedium, + elevation: 0, + toolbarHeight: 64, + backgroundColor: UIColors.white, + toolbarTextStyle: _lightTextTheme.titleLarge, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + ); + } + + /// Returns the correct [AppBarTheme] based on the current theme. + AppBarTheme get _darkAppBarTheme { + return _lightAppBarTheme.copyWith( + backgroundColor: UIColors.grey, + iconTheme: _darkIconTheme, + toolbarTextStyle: _darkTextTheme.titleLarge, + titleTextStyle: _darkTextTheme.titleMedium, + ); + } + + /// Returns the correct [IconThemeData] based on the current theme. + IconThemeData get _lightIconTheme { + return const IconThemeData( + color: UIColors.black, + ); + } + + /// Returns the correct [IconThemeData] based on the current theme. + IconThemeData get _darkIconTheme { + return const IconThemeData( + color: UIColors.white, + ); + } + + /// Returns the correct [DividerThemeData] based on the current theme. + DividerThemeData get _dividerTheme { + return const DividerThemeData( + color: UIColors.outlineLight, + space: UISpacing.lg, + thickness: UISpacing.xxxs, + indent: UISpacing.lg, + endIndent: UISpacing.lg, + ); + } + + /// Returns the correct [TextTheme] based on the current theme. + TextTheme get _lightTextTheme => lightUITextTheme; + + /// Returns the correct [TextTheme] based on the current theme. + TextTheme get _darkTextTheme { + return _lightTextTheme.apply( + bodyColor: UIColors.white, + displayColor: UIColors.white, + decorationColor: UIColors.white, + ); + } + + /// The UI text theme based on [UITextStyle]. + static final lightUITextTheme = TextTheme( + displayLarge: UITextStyle.headline1, + displayMedium: UITextStyle.headline2, + displaySmall: UITextStyle.headline3, + titleMedium: UITextStyle.subtitle1, + titleSmall: UITextStyle.subtitle2, + bodyLarge: UITextStyle.bodyText1, + bodyMedium: UITextStyle.bodyText2, + labelLarge: UITextStyle.button, + bodySmall: UITextStyle.caption, + labelSmall: UITextStyle.overline, + ).apply( + bodyColor: UIColors.black, + displayColor: UIColors.black, + decorationColor: UIColors.black, + ); + + /// Returns the correct [ChipThemeData] based on the current theme. + ChipThemeData get _chipTheme { + return ChipThemeData( + backgroundColor: UIColors.secondary.shade300, + disabledColor: _backgroundColor, + selectedColor: UIColors.secondary.shade700, + secondarySelectedColor: UIColors.secondary.shade700, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + labelStyle: UITextStyle.button.copyWith(color: UIColors.black), + secondaryLabelStyle: + UITextStyle.labelSmall.copyWith(color: UIColors.white), + brightness: Brightness.light, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(22), + side: const BorderSide(), + ), + ); + } + + /// Returns the correct [ChipThemeData] based on the current theme. + ChipThemeData get _darkChipTheme { + return _chipTheme.copyWith( + backgroundColor: UIColors.white, + disabledColor: _backgroundColor, + selectedColor: UIColors.secondary.shade700, + secondarySelectedColor: UIColors.secondary.shade700, + labelStyle: UITextStyle.button.copyWith( + color: UIColors.secondary.shade700, + ), + secondaryLabelStyle: UITextStyle.labelSmall.copyWith( + color: UIColors.black, + ), + shape: RoundedRectangleBorder( + side: const BorderSide(color: UIColors.white, width: 2), + borderRadius: BorderRadius.circular(22), + ), + ); + } + + /// Returns the correct [ButtonThemeData] based on the current theme. + ButtonThemeData get _buttonTheme { + return ButtonThemeData( + textTheme: ButtonTextTheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UISpacing.sm), + ), + padding: const EdgeInsets.symmetric(vertical: UISpacing.lg), + buttonColor: UIColors.blue, + disabledColor: UIColors.lightGrey, + height: 48, + minWidth: 48, + ); + } + + /// Returns the correct [ElevatedButtonThemeData] based on the current theme. + ElevatedButtonThemeData get _elevatedButtonTheme { + return ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + padding: const EdgeInsets.symmetric(vertical: UISpacing.lg), + textStyle: _lightTextTheme.labelLarge?.copyWith( + fontWeight: UIFontWeight.bold, + ), + backgroundColor: UIColors.blue, + ), + ); + } + + /// Returns the correct [TextButtonThemeData] based on the current theme. + TextButtonThemeData get _textButtonTheme { + return TextButtonThemeData( + style: TextButton.styleFrom( + textStyle: _lightTextTheme.labelLarge?.copyWith( + fontWeight: UIFontWeight.light, + ), + foregroundColor: UIColors.black, + ), + ); + } + + /// Returns the correct [TextButtonThemeData] based on the current theme. + TextButtonThemeData get _darkTextButtonTheme { + return TextButtonThemeData( + style: TextButton.styleFrom( + textStyle: _lightTextTheme.labelLarge?.copyWith( + fontWeight: UIFontWeight.light, + ), + foregroundColor: UIColors.white, + ), + ); + } + + /// Returns the correct [BottomSheetThemeData] based on the current theme. + BottomSheetThemeData get _lightBottomSheetTheme { + return const BottomSheetThemeData( + backgroundColor: UIColors.modalBackground, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(UISpacing.lg), + topRight: Radius.circular(UISpacing.lg), + ), + ), + ); + } + + /// Returns the correct [BottomSheetThemeData] based on the current theme. + BottomSheetThemeData get _darkBottomSheetTheme { + return _lightBottomSheetTheme.copyWith( + backgroundColor: UIColors.grey, + ); + } + + /// Returns the correct [ListTileThemeData] based on the current theme. + ListTileThemeData get _listTileTheme { + return const ListTileThemeData( + iconColor: UIColors.onBackground, + contentPadding: EdgeInsets.all(UISpacing.lg), + ); + } + + /// Returns the correct [SwitchThemeData] based on the current theme. + SwitchThemeData get _switchTheme { + return SwitchThemeData( + thumbColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return UIColors.darkAqua; + } + return UIColors.black; + }), + trackColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return UIColors.primaryContainer; + } + return UIColors.paleSky; + }), + ); + } + + /// Returns the correct [ProgressIndicatorThemeData] based on the current + /// theme. + ProgressIndicatorThemeData get _progressIndicatorTheme { + return const ProgressIndicatorThemeData( + color: UIColors.darkAqua, + circularTrackColor: UIColors.borderOutline, + ); + } + + /// Returns the correct [TabBarTheme] based on the current theme. + TabBarTheme get _tabBarTheme { + return TabBarTheme( + labelStyle: UITextStyle.button, + labelColor: UIColors.darkAqua, + labelPadding: const EdgeInsets.symmetric( + horizontal: UISpacing.lg, + vertical: UISpacing.md + UISpacing.xxs, + ), + unselectedLabelStyle: UITextStyle.button, + unselectedLabelColor: UIColors.mediumEmphasisSurface, + indicator: const UnderlineTabIndicator( + borderSide: BorderSide( + width: 3, + color: UIColors.darkAqua, + ), + ), + indicatorSize: TabBarIndicatorSize.label, + ); + } + + /// Returns the correct [BottomNavigationBarThemeData] based on the current + /// theme. + BottomNavigationBarThemeData get _bottomAppBarTheme { + return BottomNavigationBarThemeData( + backgroundColor: UIColors.black, + selectedItemColor: UIColors.white, + unselectedItemColor: UIColors.white.withOpacity(0.74), + ); + } + + /// Returns the correct [OutlinedButtonThemeData] based on the current theme. + OutlinedButtonThemeData get _lightOutlinedButtonTheme { + return OutlinedButtonThemeData( + style: ButtonStyle( + shape: WidgetStateProperty.all(const StadiumBorder()), + backgroundColor: WidgetStateColor.resolveWith( + (states) => UIColors.white, + ), + side: WidgetStateProperty.resolveWith( + (states) => const BorderSide(), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UISpacing.xlg, + vertical: UISpacing.lg, + ), + ), + alignment: Alignment.center, + textStyle: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.disabled) + ? UITextStyle.button.copyWith( + color: UIColors.black, + fontWeight: FontWeight.w500, + ) + : UITextStyle.button.copyWith( + color: states.contains(WidgetState.disabled) + ? UIColors.black + : UIColors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } + + /// Returns the correct [OutlinedButtonThemeData] based on the current theme. + OutlinedButtonThemeData get _darkOutlinedButtonTheme { + return OutlinedButtonThemeData( + style: ButtonStyle( + shape: WidgetStateProperty.all(const StadiumBorder()), + backgroundColor: WidgetStateColor.resolveWith( + (states) => UIColors.black, + ), + side: WidgetStateProperty.resolveWith( + (states) => const BorderSide(color: UIColors.white), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UISpacing.xlg, + vertical: UISpacing.lg, + ), + ), + alignment: Alignment.center, + textStyle: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.disabled) + ? UITextStyle.button.copyWith( + color: UIColors.white, + fontWeight: FontWeight.w500, + ) + : UITextStyle.button.copyWith( + color: states.contains(WidgetState.disabled) + ? UIColors.black + : UIColors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart b/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart new file mode 100644 index 0000000..5f73655 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart @@ -0,0 +1,31 @@ +import 'package:flutter/widgets.dart'; + +/// Namespace for Default App Font Weights +abstract class UIFontWeight { + /// FontWeight value of `w900` + static const FontWeight black = FontWeight.w900; + + /// FontWeight value of `w800` + static const FontWeight extraBold = FontWeight.w800; + + /// FontWeight value of `w700` + static const FontWeight bold = FontWeight.w700; + + /// FontWeight value of `w600` + static const FontWeight semiBold = FontWeight.w600; + + /// FontWeight value of `w500` + static const FontWeight medium = FontWeight.w500; + + /// FontWeight value of `w400` + static const FontWeight regular = FontWeight.w400; + + /// FontWeight value of `w300` + static const FontWeight light = FontWeight.w300; + + /// FontWeight value of `w200` + static const FontWeight extraLight = FontWeight.w200; + + /// FontWeight value of `w100` + static const FontWeight thin = FontWeight.w100; +} diff --git a/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart b/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart new file mode 100644 index 0000000..3b5bd1c --- /dev/null +++ b/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// TextStyles for the app +abstract class UITextStyle { + /// Base Text Style + static const _baseTextStyle = TextStyle( + package: 'todo_ui', + fontWeight: UIFontWeight.regular, + fontFamily: 'NotoSansDisplay', + decoration: TextDecoration.none, + textBaseline: TextBaseline.alphabetic, + ); + + /// Display 2 Text Style + static final TextStyle display2 = _baseTextStyle.copyWith( + fontSize: 57, + fontWeight: UIFontWeight.bold, + height: 1.12, + letterSpacing: -0.25, + ); + + /// Display 3 Text Style + static final TextStyle display3 = _baseTextStyle.copyWith( + fontSize: 45, + fontWeight: UIFontWeight.bold, + height: 1.15, + ); + + /// Headline 1 Text Style + static final TextStyle headline1 = _baseTextStyle.copyWith( + fontSize: 36, + fontWeight: UIFontWeight.bold, + height: 1.22, + ); + + /// Headline 2 Text Style + static final TextStyle headline2 = _baseTextStyle.copyWith( + fontSize: 32, + fontWeight: UIFontWeight.bold, + height: 1.25, + ); + + /// Headline 3 Text Style + static final TextStyle headline3 = _baseTextStyle.copyWith( + fontSize: 28, + fontWeight: UIFontWeight.semiBold, + height: 1.28, + ); + + /// Subtitle 1 Text Style + static final TextStyle subtitle1 = _baseTextStyle.copyWith( + fontSize: 16, + height: 1.5, + letterSpacing: 0.1, + ); + + /// Subtitle 2 Text Style + static final TextStyle subtitle2 = _baseTextStyle.copyWith( + fontSize: 14, + height: 1.42, + letterSpacing: 0.1, + ); + + /// Body Text 1 Text Style + static final TextStyle bodyText1 = _baseTextStyle.copyWith( + fontSize: 16, + height: 1.5, + letterSpacing: 0.5, + ); + + /// Body Text 2 Text Style (the default) + static final TextStyle bodyText2 = _baseTextStyle.copyWith( + fontSize: 14, + height: 1.42, + letterSpacing: 0.25, + ); + + /// Caption Text Style + static final TextStyle caption = _baseTextStyle.copyWith( + fontSize: 12, + height: 1.33, + letterSpacing: 0.4, + ); + + /// Button Text Style + static final TextStyle button = _baseTextStyle.copyWith( + fontSize: 16, + height: 1.42, + letterSpacing: 0.1, + ); + + /// Overline Text Style + static final TextStyle overline = _baseTextStyle.copyWith( + fontSize: 12, + height: 1.33, + letterSpacing: 0.5, + ); + + /// Label Small Text Style + static final TextStyle labelSmall = _baseTextStyle.copyWith( + fontSize: 11, + height: 1.45, + letterSpacing: 0.5, + ); +} diff --git a/todo/packages/todo_ui/lib/src/typography/typography.dart b/todo/packages/todo_ui/lib/src/typography/typography.dart new file mode 100644 index 0000000..732bcc0 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/typography/typography.dart @@ -0,0 +1,2 @@ +export 'todo_font_weight.dart'; +export 'todo_text_styles.dart'; diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart b/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart new file mode 100644 index 0000000..0121fdd --- /dev/null +++ b/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// {@template app_app_bar} +/// A custom app bar widget. +/// {@endtemplate} +class UIAppBar extends StatelessWidget implements PreferredSizeWidget { + /// {@macro app_app_bar} + const UIAppBar({ + super.key, + this.widgetTitle, + this.textTitle, + this.titleTextStyle, + this.leading, + this.backgroundColor, + this.elevation, + this.centerTitle, + this.toolbarHeight, + this.leadingWidth, + this.actions, + this.buttonStyle, + this.automaticallyImplyLeading, + }); + + /// The AppBar if the user wants to add actions. + UIAppBar.actionAppBar({ + super.key, + this.textTitle, + this.titleTextStyle, + this.leading, + this.backgroundColor, + this.centerTitle, + this.leadingWidth, + this.buttonStyle, + }) : toolbarHeight = 50, + actions = [ + TextButton( + style: buttonStyle, + onPressed: () {}, + child: const Text('Action 1'), + ), + ], + elevation = 3, + widgetTitle = Text(textTitle!), + automaticallyImplyLeading = false; + + /// Red color AppBar. + UIAppBar.redAppBar({ + super.key, + this.textTitle, + this.titleTextStyle, + this.leading, + this.actions, + this.buttonStyle, + this.backgroundColor = UIColors.red, + this.automaticallyImplyLeading, + }) : elevation = 5, + widgetTitle = Text(textTitle!), + centerTitle = true, + toolbarHeight = 50, + leadingWidth = 60; + + /// The text for the title of the app bar is a Text. + final String? textTitle; + + /// The title of the app bar. + final Widget? widgetTitle; + + /// The style of the title text. + final TextStyle? titleTextStyle; + + /// The leading widget of the app bar. + final Widget? leading; + + /// The background color of the app bar. + final Color? backgroundColor; + + /// The elevation of the app bar. + final double? elevation; + + /// Whether the title should be centered. + final bool? centerTitle; + + /// The height of the app bar. + final double? toolbarHeight; + + /// The width of the leading widget. + final double? leadingWidth; + + /// The actions of the app bar. + final List? actions; + + /// The style of the button of actions from the appbar. + final ButtonStyle? buttonStyle; + + /// Whether the leading widget should be automatically implied if set to false + /// the automatic leading widget will not be shown. + final bool? automaticallyImplyLeading; + + @override + Widget build(BuildContext context) { + return AppBar( + title: widgetTitle, + titleTextStyle: + titleTextStyle ?? Theme.of(context).appBarTheme.titleTextStyle, + leading: leading, + backgroundColor: + backgroundColor ?? Theme.of(context).appBarTheme.backgroundColor, + elevation: elevation, + centerTitle: centerTitle, + toolbarHeight: toolbarHeight, + leadingWidth: leadingWidth, + actions: actions, + automaticallyImplyLeading: automaticallyImplyLeading ?? true, + ); + } + + @override + Size get preferredSize => Size.fromHeight(toolbarHeight ?? kToolbarHeight); +} diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart b/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart new file mode 100644 index 0000000..ca71171 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// {@template outlined_button} +/// A button component based on material [OutlinedButton] widget +/// {@endtemplate} +class UIOutlinedButton extends StatelessWidget { + /// {@macro outlined_button} + const UIOutlinedButton({ + required this.text, + super.key, + this.backgroundColor, + this.darkBackgroundColor, + this.textColor, + this.onPressed, + this.borderColor, + this.icon, + }); + + /// Primary button which background color is the primary color of the theme + const UIOutlinedButton.primary({ + required this.text, + super.key, + this.onPressed, + this.icon, + }) : backgroundColor = UIColors.lightBlue200, + darkBackgroundColor = UIColors.blue, + borderColor = UIColors.white, + textColor = UIColors.black; + + /// Secondary button which background color + /// is the secondary color of the theme + const UIOutlinedButton.secondary({ + required this.text, + super.key, + this.onPressed, + this.icon, + }) : backgroundColor = UIColors.grey, + darkBackgroundColor = UIColors.grey, + borderColor = UIColors.white, + textColor = UIColors.white; + + /// Text on the button + final String text; + + /// Callback when the button is pressed + final VoidCallback? onPressed; + + /// Background color of the button + final Color? backgroundColor; + + /// Background color of the button in dark mode + final Color? darkBackgroundColor; + + /// Text color of the button + final Color? textColor; + + /// Border color of the button + final Color? borderColor; + + /// Icon on the button + final Widget? icon; + + @override + Widget build(BuildContext context) { + final isInactive = onPressed == null; + final theme = Theme.of(context); + return OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + shape: const StadiumBorder(), + disabledBackgroundColor: theme.colorScheme.surface, + backgroundColor: backgroundColor, + side: borderColor == null + ? null + : BorderSide( + color: onPressed == null + ? (theme.colorScheme.onSurface) + : borderColor!, + ), + padding: const EdgeInsets.symmetric( + horizontal: UISpacing.md, + vertical: UISpacing.md, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) + Padding( + padding: const EdgeInsets.only(right: 10), + child: icon, + ), + SizedBox( + child: Text( + text, + textAlign: TextAlign.center, + style: UITextStyle.button.copyWith( + color: + isInactive ? (theme.colorScheme.onSurface) : textColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart b/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart new file mode 100644 index 0000000..3ece3ff --- /dev/null +++ b/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:todo_ui/todo_ui.dart'; + +/// {@template text_field} +/// A text field component based on material [TextFormField] widget which +/// extends TextField with email icon and validates input type. +/// {@endtemplate} +class UITextField extends StatelessWidget { + /// {@macro text_field} + const UITextField({ + super.key, + this.initialValue, + this.autoFillHints, + this.controller, + this.inputFormatters, + this.autocorrect = true, + this.readOnly = false, + this.hintText, + this.errorText, + this.prefix, + this.suffix, + this.keyboardType, + this.onChanged, + this.onSubmitted, + this.onTap, + this.obscureText, + }); + + /// A text field component based on material [TextFormField] widget which + /// extends TextField with email icon and validates input type. + const UITextField.emailTextField({ + super.key, + this.initialValue, + this.controller, + this.inputFormatters, + this.readOnly = false, + this.hintText, + this.errorText, + this.suffix, + this.onChanged, + this.onSubmitted, + this.onTap, + this.obscureText, + }) : keyboardType = TextInputType.emailAddress, + autoFillHints = const [AutofillHints.email], + prefix = const Padding( + padding: EdgeInsets.only( + left: UISpacing.sm, + right: UISpacing.sm, + ), + child: Icon( + Icons.email, + color: UIColors.grey, + ), + ), + autocorrect = false; + + /// A password text field component based on material [TextFormField] + /// widget which extends TextField which obscures the text + const UITextField.passwordTextField({ + super.key, + this.initialValue, + this.controller, + this.inputFormatters, + this.readOnly = false, + this.errorText, + this.suffix, + this.onChanged, + this.onSubmitted, + this.onTap, + this.keyboardType, + this.autoFillHints, + this.prefix, + this.obscureText = true, + }) : hintText = 'Password', + autocorrect = false; + + /// A value to initialize the field to. + final String? initialValue; + + /// List of auto fill hints. + final Iterable? autoFillHints; + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController] and + /// initialize its [TextEditingController.text] with [initialValue]. + final TextEditingController? controller; + + /// Optional input validation and formatting overrides. + final List? inputFormatters; + + /// Whether to enable autocorrect. + /// Defaults to true. + final bool autocorrect; + + /// Whether the text field should be read-only. + /// Defaults to false. + final bool readOnly; + + /// Text that suggests what sort of input the field accepts. + final String? hintText; + + /// Text that appears below the field. + final String? errorText; + + /// A widget that appears before the editable part of the text field. + final Widget? prefix; + + /// A widget that appears after the editable part of the text field. + final Widget? suffix; + + /// The type of keyboard to use for editing the text. + /// Defaults to [TextInputType.text] if maxLines is one and + /// [TextInputType.multiline] otherwise. + final TextInputType? keyboardType; + + /// Called when the user inserts or deletes texts in the text field. + final ValueChanged? onChanged; + + /// {@macro flutter.widgets.editableText.onSubmitted} + final ValueChanged? onSubmitted; + + /// Called when the text field has been tapped. + final VoidCallback? onTap; + + /// Whether the text field should obscure the text being edited. + final bool? obscureText; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 80), + child: TextFormField( + key: key, + initialValue: initialValue, + controller: controller, + inputFormatters: inputFormatters, + keyboardType: keyboardType, + obscureText: obscureText ?? false, + autocorrect: autocorrect, + readOnly: readOnly, + autofillHints: autoFillHints, + cursorColor: Theme.of(context).colorScheme.onSurface, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w500, + ), + onFieldSubmitted: onSubmitted, + decoration: InputDecoration( + hintText: hintText, + hintStyle: Theme.of(context).textTheme.labelLarge, + errorText: errorText, + prefixIcon: prefix, + suffixIcon: suffix, + suffixIconConstraints: const BoxConstraints.tightFor( + width: 32, + height: 32, + ), + prefixIconConstraints: const BoxConstraints.tightFor( + width: 48, + ), + ), + onChanged: onChanged, + onTap: onTap, + ), + ); + } +} diff --git a/todo/packages/todo_ui/lib/src/widgets/widgets.dart b/todo/packages/todo_ui/lib/src/widgets/widgets.dart new file mode 100644 index 0000000..c8dd9e5 --- /dev/null +++ b/todo/packages/todo_ui/lib/src/widgets/widgets.dart @@ -0,0 +1,3 @@ +export 'todo_app_bar.dart'; +export 'todo_outlined_button.dart'; +export 'todo_text_field.dart'; diff --git a/todo/packages/todo_ui/lib/todo_ui.dart b/todo/packages/todo_ui/lib/todo_ui.dart new file mode 100644 index 0000000..c54186f --- /dev/null +++ b/todo/packages/todo_ui/lib/todo_ui.dart @@ -0,0 +1,7 @@ +export 'src/colors/todo_colors.dart'; +export 'src/extensions/extensions.dart'; +export 'src/icons/icons.dart'; +export 'src/spacing/todo_spacing.dart'; +export 'src/theme/todo_theme.dart'; +export 'src/typography/typography.dart'; +export 'src/widgets/widgets.dart'; diff --git a/todo/packages/todo_ui/pubspec.yaml b/todo/packages/todo_ui/pubspec.yaml new file mode 100644 index 0000000..82de7ee --- /dev/null +++ b/todo/packages/todo_ui/pubspec.yaml @@ -0,0 +1,44 @@ +name: todo_ui +description: Todo UI library +version: 0.0.1 +publish_to: none + +environment: + sdk: ^3.5.0 + flutter: 3.24.3 + +dependencies: + flutter: + sdk: flutter + flutter_asset_generator: ^1.2.0 + flutter_svg: ^2.0.10+1 + +dev_dependencies: + build_runner: ^2.4.13 + flutter_gen_runner: ^5.7.0 + flutter_test: + sdk: flutter + mockingjay: ^0.6.0 + very_good_analysis: ^6.0.0 + +flutter: + uses-material-design: true + assets: + - assets/icons/ + - assets/images/ + fonts: + - family: NotoSansDisplay + fonts: + - asset: assets/fonts/NotoSansDisplay-Regular.ttf + weight: 400 + - asset: assets/fonts/NotoSansDisplay-SemiBold.ttf + weight: 600 + +flutter_gen: + assets: + output: + package_parameter_enabled: true + line_length: 80 + + integrations: + flutter_svg: true diff --git a/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart b/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart new file mode 100644 index 0000000..ff39710 --- /dev/null +++ b/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + group('ThemeX Extension Tests', () { + test('Test light mode icons', () { + final lightTheme = ThemeData(brightness: Brightness.light); + expect(lightTheme.icons, isA()); + }); + + test('Test dark mode icons', () { + final darkTheme = ThemeData(brightness: Brightness.dark); + expect(darkTheme.icons, isA()); + }); + }); +} diff --git a/todo/packages/todo_ui/test/src/helpers/helpers.dart b/todo/packages/todo_ui/test/src/helpers/helpers.dart new file mode 100644 index 0000000..b15fe65 --- /dev/null +++ b/todo/packages/todo_ui/test/src/helpers/helpers.dart @@ -0,0 +1 @@ +export 'pump_app.dart'; diff --git a/todo/packages/todo_ui/test/src/helpers/pump_app.dart b/todo/packages/todo_ui/test/src/helpers/pump_app.dart new file mode 100644 index 0000000..7b4c6e9 --- /dev/null +++ b/todo/packages/todo_ui/test/src/helpers/pump_app.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart'; + +extension UITester on WidgetTester { + Future pumpApp( + Widget widgetUnderTest, { + ThemeData? theme, + MockNavigator? navigator, + }) async { + await pumpWidget( + MaterialApp( + theme: theme, + home: navigator == null + ? Scaffold( + body: widgetUnderTest, + ) + : MockNavigatorProvider( + navigator: navigator, + child: Scaffold( + body: widgetUnderTest, + ), + ), + ), + ); + await pump(); + } +} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart new file mode 100644 index 0000000..3e578ba --- /dev/null +++ b/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + group('UIIconsDark Tests', () { + test('Test Back icon', () { + final appIcons = UIIconsDark(); + final svgPicture = appIcons.backIcon(); + + expect(svgPicture, isA()); + }); + + test('Test email logo icon', () { + final appIcons = UIIconsDark(); + final svgPicture = appIcons.emailOutline(); + + expect(svgPicture, isA()); + }); + }); +} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart new file mode 100644 index 0000000..c9c532f --- /dev/null +++ b/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + group('UIIconsLight Tests', () { + test('Test Back icon', () { + final appIcons = UIIconsLight(); + final svgPicture = appIcons.backIcon(); + + expect(svgPicture, isA()); + }); + + test('Test email logo icon', () { + final appIcons = UIIconsLight(); + final svgPicture = appIcons.emailOutline(); + + expect(svgPicture, isA()); + }); + }); +} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart new file mode 100644 index 0000000..9b82685 --- /dev/null +++ b/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +class TestUIIcon extends UIIcon {} + +void main() { + group('UIIcons Tests', () { + test('Test Back icon', () { + final appIcons = TestUIIcon(); + final svgPicture = appIcons.backIcon(); + + expect(svgPicture, isA()); + }); + + test('Test email logo icon', () { + final appIcons = TestUIIcon(); + final svgPicture = appIcons.emailOutline(); + + expect(svgPicture, isA()); + }); + }); +} diff --git a/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart b/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart new file mode 100644 index 0000000..24c51d7 --- /dev/null +++ b/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + group('TextStyles', () { + group('UITextStyle', () { + test('display2 returns TextStyle', () { + expect(UITextStyle.display2, isA()); + }); + + test('display3 returns TextStyle', () { + expect(UITextStyle.display3, isA()); + }); + + test('headline1 returns TextStyle', () { + expect(UITextStyle.headline1, isA()); + }); + + test('headline2 returns TextStyle', () { + expect(UITextStyle.headline2, isA()); + }); + + test('headline3 returns TextStyle', () { + expect(UITextStyle.headline3, isA()); + }); + + test('subtitle1 returns TextStyle', () { + expect(UITextStyle.subtitle1, isA()); + }); + + test('subtitle2 returns TextStyle', () { + expect(UITextStyle.subtitle2, isA()); + }); + + test('bodyText1 returns TextStyle', () { + expect(UITextStyle.bodyText1, isA()); + }); + + test('bodyText2 returns TextStyle', () { + expect(UITextStyle.bodyText2, isA()); + }); + + test('button returns TextStyle', () { + expect(UITextStyle.button, isA()); + }); + + test('caption returns TextStyle', () { + expect(UITextStyle.caption, isA()); + }); + + test('overline returns TextStyle', () { + expect(UITextStyle.overline, isA()); + }); + + test('labelSmall returns TextStyle', () { + expect(UITextStyle.labelSmall, isA()); + }); + }); + }); +} diff --git a/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart new file mode 100644 index 0000000..a33f031 --- /dev/null +++ b/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; +void main() { + testWidgets('UIAppBar displays with actions', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: UIAppBar.actionAppBar( + textTitle: 'Action AppBar', + ), + ), + ), + ); + + expect(find.text('Action AppBar'), findsOneWidget); + + expect(find.text('Action 1'), findsOneWidget); + + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.elevation, 3); + }); + + testWidgets('UIAppBar displays with red background color', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: UIAppBar.redAppBar( + textTitle: 'Red AppBar', + ), + ), + ), + ); + + expect(find.text('Red AppBar'), findsOneWidget); + + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.backgroundColor, UIColors.red); + + expect(appBar.elevation, 5); + }); +} diff --git a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart new file mode 100644 index 0000000..877a60e --- /dev/null +++ b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + testWidgets('UIOutlinedButton displays default properties', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: UIOutlinedButton( + text: 'Default Button', + onPressed: () {}, + ), + ), + ), + ); + + expect(find.text('Default Button'), findsOneWidget); + + final outlinedButton = + tester.widget(find.byType(OutlinedButton)); + expect(outlinedButton.onPressed, isNotNull); + }); + + testWidgets('UIOutlinedButton primary displays correct properties', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: UIOutlinedButton.primary( + text: 'Primary Button', + onPressed: () {}, + ), + ), + ), + ); + + expect(find.text('Primary Button'), findsOneWidget); + + final outlinedButton = + tester.widget(find.byType(OutlinedButton)); + expect(outlinedButton.onPressed, isNotNull); + + final container = + tester.widget(find.byType(OutlinedButton)); + expect(container.style?.backgroundColor?.resolve({}), UIColors.lightBlue200); + + final textWidget = tester.widget(find.text('Primary Button')); + expect(textWidget.style?.color, UIColors.black); + }); + + testWidgets('UIOutlinedButton secondary displays correct properties', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: UIOutlinedButton.secondary( + text: 'Secondary Button', + onPressed: () {}, + ), + ), + ), + ); + + expect(find.text('Secondary Button'), findsOneWidget); + + final outlinedButton = + tester.widget(find.byType(OutlinedButton)); + expect(outlinedButton.onPressed, isNotNull); + + final container = + tester.widget(find.byType(OutlinedButton)); + expect(container.style?.backgroundColor?.resolve({}), UIColors.grey); + + final textWidget = tester.widget(find.text('Secondary Button')); + expect(textWidget.style?.color, UIColors.white); + }); + + testWidgets('UIOutlinedButton displays with icon', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: UIOutlinedButton( + text: 'Button with Icon', + icon: const Icon(Icons.add), + onPressed: () {}, + ), + ), + ), + ); + + expect(find.text('Button with Icon'), findsOneWidget); + + expect(find.byIcon(Icons.add), findsOneWidget); + }); + + testWidgets('UIOutlinedButton displays as disabled when onPressed is null', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UIOutlinedButton( + text: 'Disabled Button', + ), + ), + ), + ); + + expect(find.text('Disabled Button'), findsOneWidget); + + final outlinedButton = + tester.widget(find.byType(OutlinedButton)); + expect(outlinedButton.onPressed, isNull); + + final theme = Theme.of(tester.element(find.byType(UIOutlinedButton))); + final textWidget = tester.widget(find.text('Disabled Button')); + expect(textWidget.style?.color, theme.colorScheme.onSurface); + }); +} \ No newline at end of file diff --git a/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart new file mode 100644 index 0000000..2e3c653 --- /dev/null +++ b/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + testWidgets('UITextField displays default properties', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UITextField( + hintText: 'Default TextField', + ), + ), + ), + ); + + expect(find.text('Default TextField'), findsOneWidget); + + final textField = tester.widget(find.byType(TextFormField)); + expect(textField.enabled, isTrue); + }); + + testWidgets('UITextField displays with initial value', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UITextField( + initialValue: 'Initial Value', + ), + ), + ), + ); + + final textField = tester.widget(find.byType(TextFormField)); + expect(textField.initialValue, 'Initial Value'); + }); + + testWidgets('UITextField displays error text', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UITextField( + hintText: 'TextField with Error', + errorText: 'Error Text', + ), + ), + ), + ); + + expect(find.text('Error Text'), findsOneWidget); + }); +} diff --git a/todo/packages/todo_ui/test/theme/todo_theme_test.dart b/todo/packages/todo_ui/test/theme/todo_theme_test.dart new file mode 100644 index 0000000..6fe826d --- /dev/null +++ b/todo/packages/todo_ui/test/theme/todo_theme_test.dart @@ -0,0 +1,324 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo_ui/todo_ui.dart'; + +void main() { + group('UILightTheme', () { + group('themeData', () { + group('color', () { + test('primary is sky blue', () { + expect( + UITheme().lightTheme.colorScheme.primary, + UIColors.skyBlue, + ); + }); + + test('secondary is UIColors.paleSky', () { + expect( + UITheme().lightTheme.colorScheme.secondary, + UIColors.paleSky, + ); + }); + }); + + group('divider', () { + test('horizontal padding is UISpacing.lg', () { + expect( + UITheme().lightTheme.dividerTheme.indent, + UISpacing.lg, + ); + expect( + UITheme().lightTheme.dividerTheme.endIndent, + UISpacing.lg, + ); + }); + + test('space is UISpacing.lg', () { + expect( + UITheme().lightTheme.dividerTheme.space, + UISpacing.lg, + ); + }); + }); + + group('switchTheme', () { + group('thumbColor', () { + test('returns darkAqua when selected', () { + expect( + UITheme() + .lightTheme + .switchTheme + .thumbColor + ?.resolve({WidgetState.selected}), + equals(UIColors.darkAqua), + ); + }); + + test('returns black when not selected', () { + expect( + UITheme().lightTheme.switchTheme.thumbColor?.resolve({}), + equals(UIColors.black), + ); + }); + }); + + group('trackColor', () { + test('returns primaryContainer when selected', () { + expect( + UITheme() + .lightTheme + .switchTheme + .trackColor + ?.resolve({WidgetState.selected}), + equals(UIColors.primaryContainer), + ); + }); + + test('returns paleSky when not selected', () { + expect( + UITheme().lightTheme.switchTheme.trackColor?.resolve({}), + equals(UIColors.paleSky), + ); + }); + }); + }); + + group('progressIndicatorTheme', () { + test('color is UIColors.darkAqua', () { + expect( + UITheme().lightTheme.progressIndicatorTheme.color, + equals(UIColors.darkAqua), + ); + }); + + test('circularTrackColor is UIColors.borderOutline', () { + expect( + UITheme() + .lightTheme + .progressIndicatorTheme + .circularTrackColor, + equals(UIColors.borderOutline), + ); + }); + }); + + group('lightOutlinedButtonTheme', () { + test('shape is StadiumBorder', () { + expect( + UITheme().lightTheme.outlinedButtonTheme.style?.shape?.resolve({}), + const StadiumBorder(), + ); + }); + + test('background color is UIColors.white', () { + expect( + UITheme() + .lightTheme + .outlinedButtonTheme + .style + ?.backgroundColor! + .resolve({}), + equals(UIColors.white), + ); + }); + + test('side is BorderSide', () { + expect( + UITheme().lightTheme.outlinedButtonTheme.style?.side!.resolve({}), + equals(const BorderSide()), + ); + }); + + test( + 'padding is EdgeInsets.symmetric(horizontal: UISpacing.xlg, vertical: UISpacing.lg)', + () { + expect( + UITheme().lightTheme.outlinedButtonTheme.style?.padding!.resolve({}), + equals( + const EdgeInsets.symmetric( + horizontal: UISpacing.xlg, + vertical: UISpacing.lg, + ), + ), + ); + }); + + test('alignment is Alignment.center', () { + expect( + UITheme().lightTheme.outlinedButtonTheme.style?.alignment, + equals(Alignment.center), + ); + }); + + test('text style for disabled state', () { + final disabledTextStyle = UITheme() + .lightTheme + .outlinedButtonTheme + .style + ?.textStyle! + .resolve({WidgetState.disabled}); + expect( + disabledTextStyle?.color, + equals(UIColors.black), + ); + expect( + disabledTextStyle?.fontWeight, + equals(FontWeight.w500), + ); + }); + + test('text style for enabled state', () { + final enabledTextStyle = UITheme() + .lightTheme + .outlinedButtonTheme + .style + ?.textStyle + ?.resolve({}); + expect( + enabledTextStyle?.color, + equals(UIColors.white), + ); + expect( + enabledTextStyle?.fontWeight, + equals(FontWeight.w500), + ); + }); + }); + }); + }); + + group('UIDarkTheme', () { + group('themeData', () { + group('color', () { + test('primary is blue', () { + expect( + UITheme().darkTheme.colorScheme.primary, + UIColors.blue, + ); + }); + + test('secondary is UIColors.paleSky', () { + expect( + UITheme().darkTheme.colorScheme.secondary, + UIColors.paleSky, + ); + }); + + test('background is black', () { + expect( + UITheme().darkTheme.colorScheme.surface, + UIColors.black, + ); + }); + }); + + group('divider', () { + test('horizontal padding is UISpacing.lg', () { + expect( + UITheme().darkTheme.dividerTheme.indent, + UISpacing.lg, + ); + expect( + UITheme().darkTheme.dividerTheme.endIndent, + UISpacing.lg, + ); + }); + + test('space is UISpacing.lg', () { + expect( + UITheme().darkTheme.dividerTheme.space, + UISpacing.lg, + ); + }); + }); + + group('darkOutlinedButtonTheme', () { + test('shape is StadiumBorder', () { + expect( + UITheme().darkTheme.outlinedButtonTheme.style?.shape?.resolve({}), + const StadiumBorder(), + ); + }); + + test('background color is UIColors.black', () { + expect( + UITheme() + .darkTheme + .outlinedButtonTheme + .style + ?.backgroundColor + ?.resolve({}), + equals(UIColors.black), + ); + }); + + test('side color is UIColors.white', () { + expect( + UITheme() + .darkTheme + .outlinedButtonTheme + .style + ?.side + ?.resolve({})?.color, + equals(UIColors.white), + ); + }); + + test( + 'padding is EdgeInsets.symmetric(horizontal: UISpacing.xlg, vertical: UISpacing.lg)', + () { + expect( + UITheme().darkTheme.outlinedButtonTheme.style?.padding?.resolve({}), + equals( + const EdgeInsets.symmetric( + horizontal: UISpacing.xlg, + vertical: UISpacing.lg, + ), + ), + ); + }); + + test('alignment is Alignment.center', () { + expect( + UITheme().darkTheme.outlinedButtonTheme.style?.alignment, + equals(Alignment.center), + ); + }); + + test('text style for disabled state', () { + final disabledTextStyle = UITheme() + .darkTheme + .outlinedButtonTheme + .style + ?.textStyle + ?.resolve({WidgetState.disabled}); + expect( + disabledTextStyle?.color, + equals(UIColors.white), + ); + expect( + disabledTextStyle?.fontWeight, + equals(FontWeight.w500), + ); + }); + + test('text style for enabled state', () { + final enabledTextStyle = UITheme() + .darkTheme + .outlinedButtonTheme + .style + ?.textStyle + ?.resolve({}); + expect( + enabledTextStyle?.color, + equals(UIColors.white), + ); + expect( + enabledTextStyle?.fontWeight, + equals(FontWeight.w500), + ); + }); + }); + }); + }); +} diff --git a/todo/packages/token_provider/.gitignore b/todo/packages/token_provider/.gitignore new file mode 100644 index 0000000..526da15 --- /dev/null +++ b/todo/packages/token_provider/.gitignore @@ -0,0 +1,7 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/todo/packages/token_provider/analysis_options.yaml b/todo/packages/token_provider/analysis_options.yaml new file mode 100644 index 0000000..4f94586 --- /dev/null +++ b/todo/packages/token_provider/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.5.1.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/token_provider/lib/src/token_provider.dart b/todo/packages/token_provider/lib/src/token_provider.dart new file mode 100644 index 0000000..02361d1 --- /dev/null +++ b/todo/packages/token_provider/lib/src/token_provider.dart @@ -0,0 +1,14 @@ +/// {@template token_provider} +/// Provides an abstraction for access token retrieval. +/// {@endtemplate} +abstract class TokenProvider { + /// {@macro token_provider} + const TokenProvider(); + + /// Fetches the current access token available in the session + /// returns null when no session is active at the moment + Future fetchToken(); + + /// Send a request to the provider to refresh the token + Future requestTokenRefresh(); +} diff --git a/todo/packages/token_provider/lib/token_provider.dart b/todo/packages/token_provider/lib/token_provider.dart new file mode 100644 index 0000000..0cfd149 --- /dev/null +++ b/todo/packages/token_provider/lib/token_provider.dart @@ -0,0 +1,4 @@ +/// Package responsible to provide an abstraction for access token retrieval +library token_provider; + +export 'src/token_provider.dart'; diff --git a/todo/packages/token_provider/pubspec.yaml b/todo/packages/token_provider/pubspec.yaml new file mode 100644 index 0000000..b08b462 --- /dev/null +++ b/todo/packages/token_provider/pubspec.yaml @@ -0,0 +1,12 @@ +name: token_provider +description: Package responsible to provide an abstraction for access token retrieval +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dev_dependencies: + mocktail: ^1.0.4 + test: ^1.25.8 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/token_provider/test/src/token_provider_test.dart b/todo/packages/token_provider/test/src/token_provider_test.dart new file mode 100644 index 0000000..18af29e --- /dev/null +++ b/todo/packages/token_provider/test/src/token_provider_test.dart @@ -0,0 +1,22 @@ +import 'package:test/test.dart'; +import 'package:token_provider/token_provider.dart'; + +class DummyTokenProvider extends TokenProvider { + @override + Future fetchToken() { + throw UnimplementedError(); + } + + @override + Future requestTokenRefresh() { + throw UnimplementedError(); + } +} + +void main() { + group('TokenProvider', () { + test('can be instantiated', () { + expect(DummyTokenProvider(), isNotNull); + }); + }); +} diff --git a/todo/packages/user_repository/.gitignore b/todo/packages/user_repository/.gitignore new file mode 100644 index 0000000..526da15 --- /dev/null +++ b/todo/packages/user_repository/.gitignore @@ -0,0 +1,7 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/todo/packages/user_repository/analysis_options.yaml b/todo/packages/user_repository/analysis_options.yaml new file mode 100644 index 0000000..97694e0 --- /dev/null +++ b/todo/packages/user_repository/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:very_good_analysis/analysis_options.6.0.0.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/todo/packages/user_repository/lib/src/exceptions/exceptions.dart b/todo/packages/user_repository/lib/src/exceptions/exceptions.dart new file mode 100644 index 0000000..6b5557e --- /dev/null +++ b/todo/packages/user_repository/lib/src/exceptions/exceptions.dart @@ -0,0 +1,39 @@ +import 'package:user_repository/user_repository.dart'; + +/// {@template user_repository_exception} +/// Base class for all [UserRepository] exceptions. +/// {@endtemplate} +abstract class UserRepositoryException implements Exception { + /// {@macro user_repository_exception} + const UserRepositoryException(this.error, this.stackTrace); + + /// The error which was caught. + final Object error; + + /// The error stack trace. + final StackTrace stackTrace; +} + +/// {@template sign_out_failure} +/// Thrown during the sign out process if a failure occurs. +/// {@endtemplate} +class SignOutFailure extends UserRepositoryException { + /// {@macro sign_out_failure} + const SignOutFailure(super.error, super.stackTrace); +} + +/// {@template user_delete_account_failure} +/// Thrown when an error occurs on the delete account process. +/// {@endtemplate} +class UserDeleteAccountFailure extends UserRepositoryException { + /// {@macro sign_out_failure} + const UserDeleteAccountFailure(super.error, super.stackTrace); +} + +/// {@template user_reauthenticate_failure} +/// Thrown when an error occurs on the reauthentication process. +/// {@endtemplate} +class UserReAuthenticateFailure extends UserRepositoryException { + /// {@macro sign_out_failure} + const UserReAuthenticateFailure(super.error, super.stackTrace); +} diff --git a/todo/packages/user_repository/lib/src/models/models.dart b/todo/packages/user_repository/lib/src/models/models.dart new file mode 100644 index 0000000..00db202 --- /dev/null +++ b/todo/packages/user_repository/lib/src/models/models.dart @@ -0,0 +1 @@ +export 'user.dart'; diff --git a/todo/packages/user_repository/lib/src/models/user.dart b/todo/packages/user_repository/lib/src/models/user.dart new file mode 100644 index 0000000..c9a3521 --- /dev/null +++ b/todo/packages/user_repository/lib/src/models/user.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'user.g.dart'; + +/// {@template user} +/// A model representing a todo user. +/// {@endtemplate} +@JsonSerializable() +class User extends Equatable { + /// {@macro user} + const User({ + required this.id, + required this.email, + }); + + /// {@macro user} + factory User.fromJson(Map json) { + return _$UserFromJson(json); + } + + /// The user unique identifer. + final String id; + + /// The user email address. + final String email; + + @override + List get props => [ + id, + email, + ]; + + /// Returns a json built from this user. + Map toJson() { + return _$UserToJson(this); + } +} diff --git a/todo/packages/user_repository/lib/src/models/user.g.dart b/todo/packages/user_repository/lib/src/models/user.g.dart new file mode 100644 index 0000000..0b08534 --- /dev/null +++ b/todo/packages/user_repository/lib/src/models/user.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +User _$UserFromJson(Map json) => User( + id: json['id'] as String, + email: json['email'] as String, + ); + +Map _$UserToJson(User instance) => { + 'id': instance.id, + 'email': instance.email, + }; diff --git a/todo/packages/user_repository/lib/src/user_repository.dart b/todo/packages/user_repository/lib/src/user_repository.dart new file mode 100644 index 0000000..4a31333 --- /dev/null +++ b/todo/packages/user_repository/lib/src/user_repository.dart @@ -0,0 +1,243 @@ +import 'dart:async'; + +import 'package:api_client/api_client.dart'; +import 'package:auth_client/auth_client.dart'; +import 'package:user_repository/user_repository.dart'; + +/// {@template user_repository} +/// A repository to handle all User related logic. +/// {@endtemplate} +class UserRepository { + /// {@macro user_repository} + UserRepository({ + required AuthenticationClient authenticationClient, + required ApiClient apiClient, + }) : _apiClient = apiClient, + _authenticationClient = authenticationClient; + + final ApiClient _apiClient; + final AuthenticationClient _authenticationClient; + + /// Maps the [AuthenticationEvent] to a [User] object. + Future onAuthStatusChanged(AuthenticationEvent event) async { + if (!event.type.isSignedIn) { + return null; + } else { + final authUser = event.user; + + final user = User( + id: authUser.id, + email: authUser.email, + ); + + return user; + } + } + + /// Emits a new value when the authentication status changes. + /// + /// If the authentication status transitions from unauthenticated to + /// authenticated, the [Stream] emits a valid [User] object. + /// + /// If the transition is from authenticated to unauthenticated, the [Stream] + /// emits null. + Stream get user { + return _authenticationClient.onAuthStatusChanged.asyncMap( + onAuthStatusChanged, + ); + } + + /// Attempts to sign up the user with [email], [password] and [name]. + /// + /// Throws a [SignUpFailure] if an exception occurs. + Future signUp({ + required String email, + required String password, + required String name, + }) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + assert( + password.isNotEmpty, + () => throw ArgumentError('Password must not be empty'), + ); + assert( + name.isNotEmpty, + () => throw ArgumentError('Name must not be empty'), + ); + try { + return _apiClient.userResource.signUp( + SignUpRequest( + email: email, + password: password, + name: name, + ), + ); + } on Exception { + rethrow; + } + } + + /// Attempts to verify email with [email] and [code]. + /// + /// Throws a [EmailVerificationFailure] if an exception occurs. + Future verifyEmail({ + required String email, + required String code, + }) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + assert( + code.isNotEmpty, + () => throw ArgumentError('Code must not be empty'), + ); + try { + return _apiClient.userResource.verifyEmail( + EmailVerificationRequest( + email: email, + code: code, + ), + ); + } on EmailVerificationFailure catch (error, stackTrace) { + throw EmailVerificationFailure(error, stackTrace); + } on Exception catch (error, stackTrace) { + throw EmailVerificationFailure(error, stackTrace); + } + } + + /// Attempts to recover password for user with [email]. + /// + /// Throws a [ForgotPasswordFailure] if an exception occurs. + Future forgotPassword({required String email}) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + try { + return _apiClient.userResource.forgotPassword(email); + } on Exception catch (error, stackTrace) { + throw ForgotPasswordFailure(error, stackTrace); + } + } + + /// Attempts to update password with given [email], [password] and [code]. + /// + /// Throws a [UpdatePasswordFailure] if an exception occurs. + Future updatePassword({ + required String email, + required String password, + required String code, + }) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + assert( + password.isNotEmpty, + () => throw ArgumentError('Password must not be empty'), + ); + assert( + code.isNotEmpty, + () => throw ArgumentError('Code must not be empty'), + ); + try { + return _apiClient.userResource.updatePassword( + UpdatePasswordRequest( + email: email, + password: password, + confirmationCode: code, + ), + ); + } on Exception catch (error, stackTrace) { + throw UpdatePasswordFailure(error, stackTrace); + } + } + + /// Attempts to resend verification code for user with [email]. + /// + /// Throws a [SendOtpCodeFailure] if an exception occurs. + Future sendOtpCode({required String email}) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + try { + return _apiClient.userResource.sendOtpCode(email); + } on SendOtpCodeFailure catch (error, stackTrace) { + throw SendOtpCodeFailure(error, stackTrace); + } on Exception catch (error, stackTrace) { + throw SendOtpCodeFailure(error, stackTrace); + } + } + + /// Attempts to sign the user identified by [email] into the MessageDesk + /// app by using the [password]. + /// + /// Throws a [SignInFailure] if an exception occurs. + Future signIn({ + required String email, + required String password, + }) async { + assert( + email.isNotEmpty, + () => throw ArgumentError('Email must not be empty'), + ); + assert( + password.isNotEmpty, + () => throw ArgumentError('Password must not be empty'), + ); + try { + return _authenticationClient.signInUser( + email: email, + password: password, + ); + } on InvalidUserFailure catch (error, stackTrace) { + throw InvalidUserFailure(error, stackTrace); + } on Exception catch (error, stackTrace) { + throw SignInFailure(error, stackTrace); + } + } + + /// Signs out the current user. + /// + /// Throws a [SignOutFailure] if an exception occurs. + Future signOut() async { + try { + await _authenticationClient.signOut(); + } catch (error, stackTrace) { + throw SignOutFailure(error, stackTrace); + } + } + + /// Deletes the current user + /// + /// Throws a [DeleteAccountFailure] if an exception occurs. + Future deleteAccount() async { + try { + await _authenticationClient.deleteAccount(); + } catch (error, stackTrace) { + throw UserDeleteAccountFailure(error, stackTrace); + } + } + + /// Re-authenticates the current user + /// + /// Throws a [UserReAuthenticateFailure] if an exception occurs. + Future reAuthenticate({ + required String email, + required String password, + }) async { + try { + await _authenticationClient.reAuthenticate( + email: email, + password: password, + ); + } catch (error, stackTrace) { + throw UserReAuthenticateFailure(error, stackTrace); + } + } +} diff --git a/todo/packages/user_repository/lib/user_repository.dart b/todo/packages/user_repository/lib/user_repository.dart new file mode 100644 index 0000000..70e7fa3 --- /dev/null +++ b/todo/packages/user_repository/lib/user_repository.dart @@ -0,0 +1,6 @@ +/// A repository to handle all User related logic. +library user_repository; + +export 'src/exceptions/exceptions.dart'; +export 'src/models/models.dart'; +export 'src/user_repository.dart'; diff --git a/todo/packages/user_repository/pubspec.yaml b/todo/packages/user_repository/pubspec.yaml new file mode 100644 index 0000000..e533333 --- /dev/null +++ b/todo/packages/user_repository/pubspec.yaml @@ -0,0 +1,22 @@ +name: user_repository +description: A repository to handle all User related logic. +version: 0.1.0+1 +publish_to: none + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + api_client: + path: ../api_client + auth_client: + path: ../auth_client + equatable: ^2.0.5 + json_annotation: ^4.9.0 + +dev_dependencies: + build_runner: ^2.4.13 + json_serializable: ^6.8.0 + mocktail: ^1.0.4 + test: ^1.25.8 + very_good_analysis: ^6.0.0 diff --git a/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart b/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart new file mode 100644 index 0000000..d40cd39 --- /dev/null +++ b/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart @@ -0,0 +1,36 @@ +import 'package:test/test.dart'; +import 'package:user_repository/user_repository.dart'; + +void main() { + group('SignOutFailure', () { + test('constructor initializes properties', () { + final error = Exception('Test error'); + final stackTrace = StackTrace.current; + + final failure = SignOutFailure(error, stackTrace); + + expect(failure.error, equals(error)); + expect(failure.stackTrace, equals(stackTrace)); + }); + + test('user delete exception', () { + final error = Exception('Test error'); + final stackTrace = StackTrace.current; + + final failure = UserDeleteAccountFailure(error, stackTrace); + + expect(failure.error, equals(error)); + expect(failure.stackTrace, equals(stackTrace)); + }); + + test('user reauthenticate exception', () { + final error = Exception('Test error'); + final stackTrace = StackTrace.current; + + final failure = UserReAuthenticateFailure(error, stackTrace); + + expect(failure.error, equals(error)); + expect(failure.stackTrace, equals(stackTrace)); + }); + }); +} diff --git a/todo/packages/user_repository/test/src/models/user_test.dart b/todo/packages/user_repository/test/src/models/user_test.dart new file mode 100644 index 0000000..67d12e1 --- /dev/null +++ b/todo/packages/user_repository/test/src/models/user_test.dart @@ -0,0 +1,48 @@ +import 'package:test/test.dart'; +import 'package:user_repository/user_repository.dart'; + +void main() { + group('User', () { + const validId = '123'; + const validEmail = 'test@example.com'; + + test('constructor initializes properties', () { + const user = User(id: validId, email: validEmail); + + expect(user.id, equals(validId)); + expect(user.email, equals(validEmail)); + }); + + test('props list contains id and email', () { + const user = User(id: validId, email: validEmail); + + expect(user.props, containsAll([validId, validEmail])); + }); + + test('supports value equality', () { + const userA = User(id: 'A', email: 'A'); + const secondUserA = User(id: 'A', email: 'A'); + const userB = User(id: 'B', email: 'B'); + + expect(userA, equals(secondUserA)); + expect(userA, isNot(equals(userB))); + }); + + test('fromJson and toJson', () { + final json = { + 'id': validId, + 'email': validEmail, + }; + + final user = User.fromJson(json); + + expect(user.id, equals(json['id'])); + expect(user.email, equals(json['email'])); + + final toJson = user.toJson(); + + expect(toJson['id'], equals(user.id)); + expect(toJson['email'], equals(user.email)); + }); + }); +} diff --git a/todo/packages/user_repository/test/src/user_repository_test.dart b/todo/packages/user_repository/test/src/user_repository_test.dart new file mode 100644 index 0000000..aa080d7 --- /dev/null +++ b/todo/packages/user_repository/test/src/user_repository_test.dart @@ -0,0 +1,546 @@ +// ignore_for_file: prefer_const_constructors +import 'package:api_client/api_client.dart'; +import 'package:auth_client/auth_client.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:user_repository/user_repository.dart'; + +class MockApiClient extends Mock implements ApiClient {} + +class MockAuthenticationClient extends Mock implements AuthenticationClient {} + +class MockUserResource extends Mock implements UserResource {} + +void main() { + group('UserRepository', () { + late ApiClient apiClient; + late AuthenticationClient authenticationClient; + late UserResource userResource; + late UserRepository repository; + + const validEmail = 'example@mail.com'; + const validPassword = 'password'; + const validCode = '12345'; + const validName = 'Name'; + final validUser = AuthenticationUser(id: 'id', email: 'email'); + + setUp(() { + apiClient = MockApiClient(); + authenticationClient = MockAuthenticationClient(); + userResource = MockUserResource(); + repository = UserRepository( + apiClient: apiClient, + authenticationClient: authenticationClient, + ); + }); + + test('can be instantiated', () { + expect( + UserRepository( + apiClient: apiClient, + authenticationClient: authenticationClient, + ), + isNotNull, + ); + }); + + test('can be instantiated', () { + expect( + UserRepository( + apiClient: apiClient, + authenticationClient: authenticationClient, + ), + isNotNull, + ); + }); + + group('Authentication status changed', () { + test('authentication event with session expired state', () async { + final authenticationEvent = + AuthenticationEvent(type: AuthEventType.sessionExpired); + final user = await repository.onAuthStatusChanged(authenticationEvent); + + expect(user, null); + }); + + test('authentication event with signed out state', () async { + final authenticationEvent = + AuthenticationEvent(type: AuthEventType.signedOut); + final user = await repository.onAuthStatusChanged(authenticationEvent); + + expect(user, null); + }); + + test('authentication event with user deleted state', () async { + final authenticationEvent = + AuthenticationEvent(type: AuthEventType.userDeleted); + final user = await repository.onAuthStatusChanged(authenticationEvent); + + expect(user, null); + }); + + test('authentication event with signed in state', () async { + final authenticationEvent = + AuthenticationEvent(type: AuthEventType.signedIn, user: validUser); + final user = await repository.onAuthStatusChanged(authenticationEvent); + + expect(user, isA()); + }); + + test('calls auth status change on AuthenticationClient', () { + when(() => authenticationClient.onAuthStatusChanged).thenAnswer( + (_) => const Stream.empty(), + ); + repository.user; + verify(() => authenticationClient.onAuthStatusChanged).called(1); + }); + }); + + group('Sign up', () { + test('signs up user success', () { + when(() => apiClient.userResource).thenReturn(userResource); + when( + () => userResource.signUp( + SignUpRequest( + email: validEmail, + password: validPassword, + name: validName, + ), + ), + ).thenAnswer((_) async {}); + + expect( + () async => repository.signUp( + email: validEmail, + password: validPassword, + name: validName, + ), + returnsNormally, + ); + }); + + test('signs up user failure', () { + when(() => apiClient.userResource.signUp(any())).thenThrow(Exception()); + + expect( + () async => repository.signUp( + email: validEmail, + password: validPassword, + name: validName, + ), + throwsA(isA()), + ); + }); + + test('signs up user with empty mail', () { + expect( + () async => repository.signUp( + email: '', + password: validPassword, + name: validName, + ), + throwsA(isA()), + ); + }); + + test('signs up user with empty password', () { + expect( + () async => repository.signUp( + email: validEmail, + password: '', + name: validName, + ), + throwsA(isA()), + ); + }); + + test('signs up user with empty name', () async { + expect( + () async => repository.signUp( + email: validEmail, + password: validPassword, + name: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Verify email', () { + test('verify mail success', () async { + when(() => apiClient.userResource).thenReturn(userResource); + when( + () => userResource.verifyEmail( + EmailVerificationRequest( + email: validEmail, + code: validCode, + ), + ), + ).thenAnswer((_) async {}); + + expect( + () async => repository.verifyEmail( + email: validEmail, + code: validCode, + ), + returnsNormally, + ); + }); + + test('verify mail failure', () async { + when(() => apiClient.userResource.signUp(any())).thenThrow(Exception()); + + expect( + () async => repository.verifyEmail( + email: validEmail, + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('verify mail failure', () async { + when(() => apiClient.userResource.signUp(any())).thenThrow( + EmailVerificationFailure(Exception(), StackTrace.empty), + ); + + expect( + () async => repository.verifyEmail( + email: validEmail, + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('verify mail with empty mail', () async { + expect( + () async => repository.verifyEmail( + email: '', + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('verify mail with empty code', () async { + expect( + () async => repository.verifyEmail( + email: validEmail, + code: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Forgot Password', () { + test('forgot password success', () async { + when(() => apiClient.userResource).thenReturn(userResource); + when(() => userResource.forgotPassword(any())).thenAnswer((_) async {}); + + expect( + () async => repository.forgotPassword( + email: validEmail, + ), + returnsNormally, + ); + }); + + test('forgot password failure', () async { + when(() => apiClient.userResource.forgotPassword(any())) + .thenThrow(Exception()); + + expect( + () async => repository.forgotPassword( + email: validEmail, + ), + throwsA(isA()), + ); + }); + + test('forgot password with empty mail', () async { + expect( + () async => repository.forgotPassword( + email: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Update password', () { + test('update password success', () async { + when(() => apiClient.userResource).thenReturn(userResource); + when( + () => userResource.updatePassword( + UpdatePasswordRequest( + email: validEmail, + password: validPassword, + confirmationCode: validCode, + ), + ), + ).thenAnswer((_) async {}); + + expect( + () async => repository.updatePassword( + email: validEmail, + password: validPassword, + code: validCode, + ), + returnsNormally, + ); + }); + + test('update password failure', () async { + when(() => apiClient.userResource.updatePassword(any())) + .thenThrow(Exception()); + + expect( + () async => repository.updatePassword( + email: validEmail, + password: validPassword, + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('update password with empty email', () async { + expect( + () async => repository.updatePassword( + email: '', + password: validPassword, + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('update password with empty password', () async { + expect( + () async => repository.updatePassword( + email: validEmail, + password: '', + code: validCode, + ), + throwsA(isA()), + ); + }); + + test('update password with empty code', () async { + expect( + () async => repository.updatePassword( + email: validEmail, + password: validPassword, + code: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Send OTP code', () { + test('otp code sent success', () async { + when(() => apiClient.userResource).thenReturn(userResource); + when(() => userResource.sendOtpCode(any())).thenAnswer((_) async {}); + + expect( + () async => repository.sendOtpCode( + email: validEmail, + ), + returnsNormally, + ); + }); + + test('otp code sent failure', () async { + when(() => apiClient.userResource.sendOtpCode(any())) + .thenThrow(Exception()); + + expect( + () async => repository.sendOtpCode( + email: validEmail, + ), + throwsA(isA()), + ); + }); + + test('otp code sent failure', () async { + when(() => apiClient.userResource.sendOtpCode(any())).thenThrow( + SendOtpCodeFailure(Exception(), StackTrace.empty), + ); + + expect( + () async => repository.sendOtpCode( + email: validEmail, + ), + throwsA(isA()), + ); + }); + + test('otp code sent with empty email', () async { + expect( + () async => repository.sendOtpCode( + email: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Sign in', () { + test('sign in success', () async { + when( + () => authenticationClient.signInUser( + email: validEmail, + password: validPassword, + ), + ).thenAnswer((_) async {}); + + expect( + () async => repository.signIn( + email: validEmail, + password: validPassword, + ), + returnsNormally, + ); + }); + + test('sign in failure', () async { + when( + () => authenticationClient.signInUser( + email: validEmail, + password: validPassword, + ), + ).thenThrow(Exception()); + + expect( + () async => repository.signIn( + email: validEmail, + password: validPassword, + ), + throwsA(isA()), + ); + }); + + test('sign in with invalid user failure', () async { + when( + () => authenticationClient.signInUser( + email: validEmail, + password: validPassword, + ), + ).thenThrow( + InvalidUserFailure(Exception(), StackTrace.empty), + ); + + expect( + () async => repository.signIn( + email: validEmail, + password: validPassword, + ), + throwsA(isA()), + ); + }); + + test('sign in with empty mail', () async { + expect( + () async => repository.signIn( + email: '', + password: validPassword, + ), + throwsA(isA()), + ); + }); + + test('verify mail with empty password', () async { + expect( + () async => repository.signIn( + email: validEmail, + password: '', + ), + throwsA(isA()), + ); + }); + }); + + group('Sign out', () { + test('sign out user succesfully', () { + when(() => authenticationClient.signOut()).thenAnswer((_) async {}); + + expect( + () async => repository.signOut(), + returnsNormally, + ); + }); + + test('sign out user unsuccesfully', () { + when(() => authenticationClient.signOut()).thenThrow(Exception()); + + expect( + () async => repository.signOut(), + throwsA(isA()), + ); + }); + }); + + group('ReAuthenticate', () { + test('reAuthenticate succesfully', () { + when( + () => authenticationClient.reAuthenticate( + email: validEmail, + password: validEmail, + ), + ).thenAnswer((_) async {}); + + expect( + () async => repository.reAuthenticate( + email: validEmail, + password: validEmail, + ), + returnsNormally, + ); + }); + + test('reauthenticate unsuccesfully', () { + when( + () => authenticationClient.reAuthenticate( + email: validEmail, + password: validEmail, + ), + ).thenThrow(Exception()); + + expect( + () async => repository.reAuthenticate( + email: validEmail, + password: validEmail, + ), + throwsA(isA()), + ); + }); + }); + + group('Delete account', () { + test('deleted account succesfully', () { + when( + () => authenticationClient.deleteAccount(), + ).thenAnswer((_) async {}); + + expect( + () async => repository.deleteAccount(), + returnsNormally, + ); + }); + + test('deleted account unsuccesfully', () { + when( + () => authenticationClient.deleteAccount(), + ).thenThrow(Exception()); + + expect( + () async => repository.deleteAccount(), + throwsA(isA()), + ); + }); + }); + }); +} diff --git a/todo/pubspec.yaml b/todo/pubspec.yaml new file mode 100644 index 0000000..85f2274 --- /dev/null +++ b/todo/pubspec.yaml @@ -0,0 +1,44 @@ +name: todo +description: A Somnio App +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ^3.5.0 + flutter: 3.24.3 + +dependencies: + api_client: + path: packages/api_client + app_config_repository: + path: packages/app_config_repository + auth_client: + path: packages/auth_client + bloc: ^8.1.4 + equatable: ^2.0.5 + flutter: + sdk: flutter + flutter_bloc: ^8.1.6 + flutter_localizations: + sdk: flutter + form_inputs: + path: packages/form_inputs + go_router: ^14.2.9 + intl: ^0.19.0 + package_info_plus: ^8.0.2 + temp_ui: + path: packages/temp_ui + url_launcher: ^6.3.0 + user_repository: + path: packages/user_repository + +dev_dependencies: + bloc_test: ^9.1.7 + flutter_test: + sdk: flutter + mocktail: ^1.0.4 + very_good_analysis: ^6.0.0 + +flutter: + uses-material-design: true + generate: true diff --git a/todo/test/app/bloc/app_bloc_test.dart b/todo/test/app/bloc/app_bloc_test.dart new file mode 100644 index 0000000..65d7fde --- /dev/null +++ b/todo/test/app/bloc/app_bloc_test.dart @@ -0,0 +1,213 @@ +import 'package:api_client/api_client.dart'; +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../helpers/pump_app.dart'; + +class MockUserRepository extends Mock implements UserRepository {} + +void main() { + late UserRepository mockUserRepository; + late AppConfigRepository appConfigRepository; + late ApiClient mockApiClient; + + setUp(() { + mockUserRepository = MockUserRepository(); + mockApiClient = MockApiClient(); + appConfigRepository = AppConfigRepository( + buildNumber: 201, + platform: Platform.android, + apiClient: mockApiClient, + ); + }); + + const testUser = User(id: 'test', email: 'test'); + + group('AppBloc', () { + blocTest( + 'emits [AppState:AppState(null, AppStatus.unauthenticated)]' + ' when nothing is added', + setUp: () { + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + expect: () => [ + const AppState(user: null, status: AppStatus.unauthenticated), + ], + ); + + blocTest( + 'emits authenticated state after AppUserChanged event', + setUp: () { + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(testUser)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + act: (bloc) => bloc.add(const AppUserChanged(user: testUser)), + expect: () => [ + const AppState.authenticated( + user: testUser, + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ], + ); + + blocTest( + 'emits down for maintenance state after ' + 'AppDownForMaintenanceStatusChanged event', + setUp: () { + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + act: (bloc) => bloc.add( + const AppDownForMaintenanceStatusChanged( + isDownForMaintenance: true, + ), + ), + expect: () => [ + const AppState.downForMaintenance( + user: testUser, + isDownForMaintenance: true, + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + ), + const AppState(user: null, status: AppStatus.unauthenticated), + ], + ); + + blocTest( + 'emits unauthenticated state after AppLogoutRequested event', + setUp: () { + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + when(() => mockUserRepository.signOut()) + .thenAnswer((invocation) => Future.value()); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + act: (bloc) => bloc.add(const AppLogoutRequested()), + expect: () => [ + const AppState.unauthenticated( + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ], + ); + + blocTest( + 'emits previous state if !isDownForMaintenance' + ' on AppDownForMaintenanceStatusChanged event', + setUp: () { + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(testUser)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + act: (bloc) => bloc.add( + const AppDownForMaintenanceStatusChanged( + isDownForMaintenance: false, + ), + ), + expect: () => [ + const AppState.authenticated( + user: testUser, + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ], + ); + blocTest( + 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' + 'AppState:AppState(null, AppStatus.forceUpgrade)]' + ' when nothing is added', + setUp: () { + appConfigRepository = AppConfigRepository( + buildNumber: 140, + platform: Platform.android, + apiClient: mockApiClient, + ); + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + expect: () => [ + const AppState(user: null, status: AppStatus.unauthenticated), + const AppState(user: null, status: AppStatus.forceUpgrade), + ], + ); + blocTest( + 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' + 'AppState:AppState(null, AppStatus.forceUpgrade)]' + ' when nothing is added', + setUp: () { + appConfigRepository = AppConfigRepository( + buildNumber: 100, + platform: Platform.iOS, + apiClient: mockApiClient, + ); + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + expect: () => [ + const AppState(user: null, status: AppStatus.unauthenticated), + const AppState(user: null, status: AppStatus.forceUpgrade), + ], + ); + blocTest( + 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' + 'AppState:AppState(null, AppStatus.downForMaintenance)]' + ' when nothing is added', + setUp: () { + appConfigRepository = AppConfigRepository( + buildNumber: 99, + platform: Platform.iOS, + apiClient: mockApiClient, + ); + when(() => mockUserRepository.user) + .thenAnswer((_) => Stream.value(null)); + }, + build: () => AppBloc( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: appConfigRepository, + ), + expect: () => [ + const AppState(user: null, status: AppStatus.unauthenticated), + const AppState(user: null, status: AppStatus.downForMaintenance), + ], + ); + }); +} diff --git a/todo/test/app/bloc/app_event_test.dart b/todo/test/app/bloc/app_event_test.dart new file mode 100644 index 0000000..dab02a3 --- /dev/null +++ b/todo/test/app/bloc/app_event_test.dart @@ -0,0 +1,53 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +void main() { + const user1 = User(id: '1', email: 'test@test.com'); + const user2 = User(id: '2', email: 'test2@test.com'); + group('AppEvent', () { + group('AppUserChanged', () { + test('supports value comparison', () { + expect( + const AppUserChanged(user: user1), + const AppUserChanged(user: user1), + ); + }); + + test('two different instances with different users are not equal', () { + expect( + const AppUserChanged(user: user1), + isNot(const AppUserChanged(user: user2)), + ); + }); + }); + + group(AppDownForMaintenanceStatusChanged, () { + test('supports comparison', () { + const appDown1 = + AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); + const appDown2 = + AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); + + expect(appDown1, equals(appDown2)); + }); + + test('two different instances with different values are not equal', () { + const appDown1 = + AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); + const appDown2 = + AppDownForMaintenanceStatusChanged(isDownForMaintenance: false); + + expect(appDown1, isNot(appDown2)); + }); + }); + + test('AppLogoutRequested supports comparison', () { + const appLogoutRequested1 = AppLogoutRequested(); + const appLogoutRequested2 = AppLogoutRequested(); + + expect(appLogoutRequested1.props, equals([])); + expect(appLogoutRequested1, equals(appLogoutRequested2)); + }); + }); +} diff --git a/todo/test/app/bloc/app_state_test.dart b/todo/test/app/bloc/app_state_test.dart new file mode 100644 index 0000000..93f1fa8 --- /dev/null +++ b/todo/test/app/bloc/app_state_test.dart @@ -0,0 +1,37 @@ +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +void main() { + const testUser1 = User(id: 'test1', email: 'test@test.com'); + const testUser2 = User(id: 'test2', email: 'test2@test.com'); + + group('AppState', () { + test('supports value comparison', () { + expect( + const AppState(user: testUser1, status: AppStatus.authenticated), + const AppState(user: testUser1, status: AppStatus.authenticated), + ); + }); + + test('two different instances with different users are not equal', () { + expect( + const AppState(user: testUser1, status: AppStatus.authenticated), + isNot(const AppState(user: testUser2, status: AppStatus.authenticated)), + ); + }); + + test('AppState copyWith', () { + const appState1 = AppState.authenticated( + user: testUser1, + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ); + final appState2 = appState1.copyWith(user: testUser2); + + expect(appState1.user, equals(testUser1)); + expect(appState2.user, equals(testUser2)); + }); + }); +} diff --git a/todo/test/app/extensions/build_context_test.dart b/todo/test/app/extensions/build_context_test.dart new file mode 100644 index 0000000..457847e --- /dev/null +++ b/todo/test/app/extensions/build_context_test.dart @@ -0,0 +1,44 @@ +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/app/app.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late AppBloc mockAppBloc; + late BuildContext mockBuildContext; + + setUp(() { + mockAppBloc = MockAppBloc(); + mockBuildContext = MockBuildContext(); + }); + + group('BuildContextX', () { + test('user getter returns the user from AppBloc state', () { + // Mock user + const testUser = User(id: 'id', email: 'email'); + + // Mock AppBloc state + when(() => mockAppBloc.state).thenReturn( + const AppState.authenticated( + user: testUser, + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ); + + // Provide the mock AppBloc using the context extension + when(() => mockBuildContext.read()).thenReturn(mockAppBloc); + + // Test the extension method + final user = mockBuildContext.user; + + // Verify that the extension method returns the correct user + expect(user, equals(testUser)); + }); + }); +} diff --git a/todo/test/app/view/app_view_test.dart b/todo/test/app/view/app_view_test.dart new file mode 100644 index 0000000..5ac3430 --- /dev/null +++ b/todo/test/app/view/app_view_test.dart @@ -0,0 +1,72 @@ +import 'dart:async'; + +import 'package:api_client/api_client.dart'; +import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/app/app.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../helpers/helpers.dart'; + +class MockUserRepository extends Mock implements UserRepository { + @override + Stream get user => Stream.value(null); +} + +void main() { + late UserRepository mockUserRepository; + late GoRouter mockRouterConfig; + late AppBloc mockAppBloc; + late ApiClient mockApiClient; + late AppConfigRepository mockAppConfigRepository; + const testUser = User(id: 'id', email: 'email'); + + setUp(() { + mockUserRepository = MockUserRepository(); + mockRouterConfig = MockRouterConfig(); + mockAppBloc = MockAppBloc(); + mockApiClient = MockApiClient(); + mockAppConfigRepository = AppConfigRepository( + buildNumber: 1, + platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, + apiClient: mockApiClient, + ); + when(() => mockAppBloc.state).thenReturn( + const AppState.unauthenticated( + forceUpgrade: ForceUpgrade(isUpgradeRequired: false), + isDownForMaintenance: false, + ), + ); + }); + + group('App', () { + testWidgets('AppWidget should build correctly', + (WidgetTester tester) async { + await tester.pumpWidget( + App( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: mockAppConfigRepository, + ), + ); + + expect(find.byType(MultiRepositoryProvider), findsOneWidget); + expect(find.byType(BlocProvider), findsOneWidget); + expect(find.byType(AppView), findsOneWidget); + }); + + testWidgets('AppView should build correctly', (WidgetTester tester) async { + await tester.pumpApp( + AppView( + routerConfig: mockRouterConfig, + ), + appBloc: mockAppBloc, + ); + + expect(find.byType(AppView), findsOneWidget); + }); + }); +} diff --git a/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart b/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart new file mode 100644 index 0000000..f26fbf5 --- /dev/null +++ b/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart @@ -0,0 +1,163 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late UserRepository mockUserRepository; + + setUp(() { + mockUserRepository = MockUserRepository(); + }); + group('DeleteAccountBloc', () { + blocTest( + 'emit [] when nothing is added', + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + expect: () => [], + ); + + blocTest( + 'emits [email] when ', + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountEmailChanged('email')), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(email: const Email.dirty('email')), + ], + ); + + blocTest( + 'emits [password] when DeleteAccountPasswordChanged is added', + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountPasswordChanged('password')), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(password: const Password.dirty('password')), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.loading] ' + 'and [DeleteAccountStatus.reauthFailure] ' + 'when DeleteAccountRequested is added', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenThrow(UserReAuthenticateFailure(Error(), StackTrace.empty)); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountRequested()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.reauthFailure), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.failure] ' + 'when DeleteAccountRequested is added and throws an exception', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenThrow(Exception()); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountRequested()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.failure), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.loading] ' + 'and [DeleteAccountStatus.success] ' + 'when DeleteAccountSubmitted is added', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenAnswer((_) async {}); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountSubmitted()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.success), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.deleteAccountFailure] ' + 'when DeleteAccountSubmitted is added and throws an exception', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenThrow(UserDeleteAccountFailure(Error(), StackTrace.empty)); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountSubmitted()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.deleteAccountFailure), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.failure] ' + 'when DeleteAccountSubmitted is added and throws an exception', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenThrow(Exception()); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountSubmitted()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.failure), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.reauthenticated] ' + 'when deleteAccount is succesful and DeleteAccountRequested is added', + setUp: () { + when( + () => mockUserRepository.deleteAccount(), + ).thenAnswer((_) async {}); + }, + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountRequested()), + expect: () => [ + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.loading), + const DeleteAccountState.initial() + .copyWith(status: DeleteAccountStatus.reauthenticated), + ], + ); + + blocTest( + 'emits [DeleteAccountStatus.initial] ' + 'when DeleteAccountResetted is added', + build: () => DeleteAccountBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const DeleteAccountResetted()), + expect: () => [ + const DeleteAccountState.initial(), + ], + ); + }); +} diff --git a/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart b/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart new file mode 100644 index 0000000..c2f7d44 --- /dev/null +++ b/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; + +void main() { + group('DeleteAccountEvent', () { + group('DeleteAccountEmailChanged', () { + test('supports value comparison', () { + expect( + const DeleteAccountEmailChanged('email'), + const DeleteAccountEmailChanged('email'), + ); + }); + + test('two different instances with different emails are not equal', () { + expect( + const DeleteAccountEmailChanged('email1'), + isNot(const DeleteAccountEmailChanged('email2')), + ); + }); + }); + + group('DeleteAccountPasswordChanged', () { + test('supports value comparison', () { + expect( + const DeleteAccountPasswordChanged('password'), + const DeleteAccountPasswordChanged('password'), + ); + }); + + test('two different instances with different passwords are not equal', + () { + expect( + const DeleteAccountPasswordChanged('password1'), + isNot(const DeleteAccountPasswordChanged('password2')), + ); + }); + }); + + group('DeleteAccountRequested', () { + test('supports value comparison', () { + expect(const DeleteAccountRequested().props, []); + expect(const DeleteAccountRequested(), const DeleteAccountRequested()); + }); + }); + + group('DeleteAccountSubmitted', () { + test('supports value comparison', () { + expect(const DeleteAccountSubmitted().props, []); + expect(const DeleteAccountSubmitted(), const DeleteAccountSubmitted()); + }); + }); + + group('DeleteAccountResetted', () { + test('supports value comparison', () { + expect(const DeleteAccountResetted().props, []); + expect(const DeleteAccountResetted(), const DeleteAccountResetted()); + }); + }); + }); +} diff --git a/todo/test/authentication/delete_account/view/delete_account_page_test.dart b/todo/test/authentication/delete_account/view/delete_account_page_test.dart new file mode 100644 index 0000000..23c6efd --- /dev/null +++ b/todo/test/authentication/delete_account/view/delete_account_page_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('DeleteAccountPage', () { + test('is a page', () { + expect(const DeleteAccountPage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = const DeleteAccountPage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [DeleteAccountView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + const DeleteAccountPage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(DeleteAccountView), findsOneWidget); + }); +} diff --git a/todo/test/authentication/delete_account/view/delete_account_view_test.dart b/todo/test/authentication/delete_account/view/delete_account_view_test.dart new file mode 100644 index 0000000..7f14f42 --- /dev/null +++ b/todo/test/authentication/delete_account/view/delete_account_view_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo_ui/todo_ui.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late DeleteAccountBloc mockDeleteAccountBloc; + + setUp(() { + mockDeleteAccountBloc = MockDeleteAccountBloc(); + when(() => mockDeleteAccountBloc.state) + .thenReturn(const DeleteAccountState.initial()); + }); + group('DeleteAccountView', () { + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpApp( + const DeleteAccountView(), + deleteAccountBloc: mockDeleteAccountBloc, + ); + + expect(find.byType(DeleteAccountForm), findsOneWidget); + }); + + testWidgets('delete account button adds [DeleteAccountSubmitted] event', + (WidgetTester tester) async { + when(() => mockDeleteAccountBloc.state).thenReturn( + const DeleteAccountState.initial().copyWith( + email: const Email.dirty('email@email.com'), + password: const Password.dirty('Password123!'), + ), + ); + await tester.pumpApp( + const DeleteAccountView(), + deleteAccountBloc: mockDeleteAccountBloc, + ); + + final submitButton = find.byType(UIOutlinedButton); + await tester.tap(submitButton); + + verify(() => mockDeleteAccountBloc.add(const DeleteAccountSubmitted())) + .called(1); + }); + + testWidgets( + 'email text field adds [DeleteAccountEmailChanged] onChanged', + (tester) async { + await tester.pumpApp( + const DeleteAccountView(), + deleteAccountBloc: mockDeleteAccountBloc, + ); + + final emailField = find.byKey(const Key('DeleteAccountEmail'),); + await tester.enterText(emailField, 'email'); + + verify( + () => mockDeleteAccountBloc.add( + const DeleteAccountEmailChanged('email'), + ), + ).called(1); + }, + ); + + testWidgets( + 'password text field adds [DeleteAccountPasswordChanged] onChanged', + (tester) async { + await tester.pumpApp( + const DeleteAccountView(), + deleteAccountBloc: mockDeleteAccountBloc, + ); + + final passwordField = find.byKey(const Key('DeleteAccountPassword'),); + await tester.enterText(passwordField, 'password'); + + verify( + () => mockDeleteAccountBloc.add( + const DeleteAccountPasswordChanged('password'), + ), + ).called(1); + }, + ); + }); +} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart new file mode 100644 index 0000000..985d0f3 --- /dev/null +++ b/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart @@ -0,0 +1,76 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late UserRepository mockUserRepository; + + setUp(() { + mockUserRepository = MockUserRepository(); + }); + + group('ForgotPasswordBloc', () { + blocTest( + 'emits [] when nothing is added', + build: () => ForgotPasswordBloc(userRepository: mockUserRepository), + expect: () => [], + ); + + blocTest( + 'emits [email] when ForgotPasswordEmailChanged is added', + build: () => ForgotPasswordBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const ForgotPasswordEmailChanged('email')), + expect: () => [ + const ForgotPasswordState.initial() + .copyWith(email: const Email.dirty('email')), + ], + ); + + blocTest( + 'emits [ForgotPasswordStatus.loading] ' + 'and [ForgotPasswordStatus.success] ' + 'when ForgotPasswordSubmitted is added', + setUp: () { + when( + () => mockUserRepository.forgotPassword(email: any(named: 'email')), + ).thenAnswer((_) async {}); + }, + build: () => ForgotPasswordBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const ForgotPasswordSubmitted()), + expect: () => [ + const ForgotPasswordState.initial().copyWith( + status: ForgotPasswordStatus.loading, + ), + const ForgotPasswordState.initial().copyWith( + status: ForgotPasswordStatus.success, + ), + ], + ); + + blocTest( + 'emits [ForgotPasswordStatus.loading] ' + 'and [ForgotPasswordStatus.error] ' + 'when ForgotPasswordSubmitted is added', + setUp: () { + when( + () => mockUserRepository.forgotPassword(email: any(named: 'email')), + ).thenThrow(Exception()); + }, + build: () => ForgotPasswordBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const ForgotPasswordSubmitted()), + expect: () => [ + const ForgotPasswordState.initial().copyWith( + status: ForgotPasswordStatus.loading, + ), + const ForgotPasswordState.initial().copyWith( + status: ForgotPasswordStatus.error, + ), + ], + ); + }); +} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart new file mode 100644 index 0000000..05e6e7b --- /dev/null +++ b/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; + +void main() { + const testEmail1 = 'test1@example.com'; + const testEmail2 = 'test2@example.com'; + + group('ForgotPasswordSubmitted', () { + test('ForgotPasswordSubmitted supports value comparison', () { + expect( + const ForgotPasswordSubmitted(), + const ForgotPasswordSubmitted(), + ); + expect( + const ForgotPasswordSubmitted().props, + const [], + ); + }); + }); + + group('ForgotPasswordEmailChanged', () { + test('supports value comparison', () { + expect( + const ForgotPasswordEmailChanged(testEmail1), + const ForgotPasswordEmailChanged(testEmail1), + ); + }); + + test('two different instances with different emails are not equal', () { + expect( + const ForgotPasswordEmailChanged(testEmail1), + isNot(const ForgotPasswordEmailChanged(testEmail2)), + ); + }); + }); +} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart new file mode 100644 index 0000000..d4958f9 --- /dev/null +++ b/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart @@ -0,0 +1,120 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; + +void main() { + const validEmail = Email.dirty('test@example.com'); + const invalidEmail = Email.dirty('invalid'); + + group('ForgotPasswordState', () { + test('supports value comparison', () { + const state1 = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: Email.pure(), + ); + + const state2 = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: Email.pure(), + ); + + expect(state1, state2); + }); + + test('copyWith method creates new instance with replaced values', () { + const originalState = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: Email.pure(), + ); + + final modifiedState = originalState.copyWith( + status: ForgotPasswordStatus.loading, + email: validEmail, + ); + + expect(modifiedState.status, ForgotPasswordStatus.loading); + expect(modifiedState.email.value, validEmail.value); + }); + + test('valid getter returns true when email is valid', () { + const state = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: validEmail, + ); + + expect(state.valid, true); + }); + + test('valid getter returns false when email is invalid', () { + const state = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: invalidEmail, + ); + + expect(state.valid, false); + }); + + test('props list contains status and email', () { + const state = ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: validEmail, + ); + + expect(state.props, [ + ForgotPasswordStatus.initial, + validEmail, + ]); + }); + + test('isInitial returns true when status is initial', () { + const status = ForgotPasswordStatus.initial; + expect(status.isInitial, isTrue); + expect(status.isLoading, isFalse); + expect(status.isLoaded, isFalse); + }); + + test('isLoading returns true when status is loading', () { + const status = ForgotPasswordStatus.loading; + expect(status.isInitial, isFalse); + expect(status.isLoading, isTrue); + expect(status.isLoaded, isFalse); + }); + + test('isLoaded returns true when status is loaded', () { + const status = ForgotPasswordStatus.loaded; + expect(status.isInitial, isFalse); + expect(status.isLoading, isFalse); + expect(status.isLoaded, isTrue); + }); + + test('initial constructor sets status to initial and email to pure', () { + const state = ForgotPasswordState.initial(); + expect(state.status, ForgotPasswordStatus.initial); + expect(state.email, const Email.pure()); + }); + + test('supports value comparision', () { + expect( + const ForgotPasswordState.initial(), + const ForgotPasswordState.initial(), + ); + }); + + test('test empty copyWith', () { + expect( + const ForgotPasswordState.initial().copyWith(), + const ForgotPasswordState.initial(), + ); + }); + + test('test initial constructor', () { + expect( + const ForgotPasswordState.initial(), + const ForgotPasswordState( + status: ForgotPasswordStatus.initial, + email: Email.pure(), + ), + ); + }); + }); +} diff --git a/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart b/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart new file mode 100644 index 0000000..c525c9e --- /dev/null +++ b/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart @@ -0,0 +1,137 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late ForgotPasswordBloc mockForgotPasswordBloc; + + setUp(() { + mockForgotPasswordBloc = MockForgotPasswordBloc(); + when(() => mockForgotPasswordBloc.state) + .thenReturn(const ForgotPasswordState.initial()); + }); + + group('SignUpView', () { + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + expect(find.byType(TextField), findsNWidgets(1)); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('shows success snackbar when reset is successful', + (WidgetTester tester) async { + whenListen( + mockForgotPasswordBloc, + Stream.fromIterable([ + const ForgotPasswordState.initial() + .copyWith(status: ForgotPasswordStatus.success), + ]), + ); + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('shows error snackbar when reset fails', + (WidgetTester tester) async { + whenListen( + mockForgotPasswordBloc, + Stream.fromIterable([ + const ForgotPasswordState.initial() + .copyWith(status: ForgotPasswordStatus.error), + ]), + ); + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('reset button is disabled when form is invalid', + (WidgetTester tester) async { + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + await tester.pumpAndSettle(); + final resetButton = find.byType(ElevatedButton); + expect(tester.widget(resetButton).enabled, isFalse); + }); + + testWidgets('reset button is enabled when form is valid', + (WidgetTester tester) async { + whenListen( + mockForgotPasswordBloc, + Stream.fromIterable([ + const ForgotPasswordState.initial() + .copyWith(email: const Email.dirty('mail@mail.com')), + ]), + ); + + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + final resetButton = find.byType(ElevatedButton); + expect(tester.widget(resetButton).enabled, isTrue); + }); + + testWidgets('should call [ForgotPasswordSubmitted] on reset button press', + (WidgetTester tester) async { + when(() => mockForgotPasswordBloc.state).thenReturn( + const ForgotPasswordState.initial().copyWith( + email: const Email.dirty('test@example.com'), + ), + ); + + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + final resetButton = find.byType(ElevatedButton); + await tester.tap(resetButton); + + verify( + () => mockForgotPasswordBloc.add( + const ForgotPasswordSubmitted(), + ), + ).called(1); + }); + + testWidgets('should call [ForgotPasswordEmailChanged] on email change', + (WidgetTester tester) async { + await tester.pumpApp( + const ForgotPasswordView(), + forgotPasswordBloc: mockForgotPasswordBloc, + ); + + final emailField = find.byType(TextField); + await tester.enterText(emailField, 'test@example.com'); + + verify( + () => mockForgotPasswordBloc.add( + const ForgotPasswordEmailChanged('test@example.com'), + ), + ).called(1); + }); + }); +} diff --git a/todo/test/authentication/login/bloc/login_bloc_test.dart b/todo/test/authentication/login/bloc/login_bloc_test.dart new file mode 100644 index 0000000..c816dc6 --- /dev/null +++ b/todo/test/authentication/login/bloc/login_bloc_test.dart @@ -0,0 +1,106 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/login/login.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late UserRepository mockUserRepository; + + setUp(() { + mockUserRepository = MockUserRepository(); + }); + group('LoginBloc', () { + blocTest( + 'emits [] when nothing is added', + build: () => LoginBloc(userRepository: mockUserRepository), + expect: () => [], + ); + + blocTest( + 'emits [email] when LoginEmailChanged is added', + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const LoginEmailChanged('email')), + expect: () => [ + const LoginState.initial().copyWith(email: const Email.dirty('email')), + ], + ); + + blocTest( + 'emits [password] when LoginPasswordChanged is added', + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const LoginPasswordChanged('password')), + expect: () => [ + const LoginState.initial() + .copyWith(password: const Password.dirty('password')), + ], + ); + + blocTest( + 'emits [obscure] as true when LoginPasswordVisibilityChanged is added', + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => + bloc.add(const LoginPasswordVisibilityChanged(obscure: true)), + expect: () => [ + const LoginState.initial().copyWith(obscurePassword: true), + ], + ); + + blocTest( + 'emits [obscure] as false when LoginPasswordVisibilityChanged is added', + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => + bloc.add(const LoginPasswordVisibilityChanged(obscure: false)), + expect: () => [ + const LoginState.initial().copyWith(obscurePassword: false), + ], + ); + + blocTest( + 'emits [FormzSubmissionStatus.inProgress] ' + 'and [FormzSubmissionStatus.success] ' + 'when LoginWithEmailAndPasswordRequested is added', + setUp: () { + when( + () => mockUserRepository.signIn( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenAnswer((_) async {}); + }, + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const LoginWithEmailAndPasswordRequested()), + expect: () => [ + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.inProgress), + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.success), + ], + ); + + blocTest( + 'emits [FormzSubmissionStatus.inProgress] ' + 'and [FormzSubmissionStatus.failure] ' + 'when LoginWithEmailAndPasswordRequested is added', + setUp: () { + when( + () => mockUserRepository.signIn( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenThrow(Exception()); + }, + build: () => LoginBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const LoginWithEmailAndPasswordRequested()), + expect: () => [ + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.inProgress), + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.failure), + ], + ); + }); +} diff --git a/todo/test/authentication/login/bloc/login_event_test.dart b/todo/test/authentication/login/bloc/login_event_test.dart new file mode 100644 index 0000000..a800d70 --- /dev/null +++ b/todo/test/authentication/login/bloc/login_event_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/login/login.dart'; + +void main() { + group('LoginEvent', () { + group('LoginEmailChanged', () { + test('supports value comparison', () { + expect( + const LoginEmailChanged('test@example.com'), + const LoginEmailChanged('test@example.com'), + ); + }); + + test('two different instances with different emails are not equal', () { + expect( + const LoginEmailChanged('test1@example.com'), + isNot(const LoginEmailChanged('test2@example.com')), + ); + }); + }); + + group('LoginPasswordChanged', () { + test('supports value comparison', () { + expect( + const LoginPasswordChanged('password123'), + const LoginPasswordChanged('password123'), + ); + }); + + test('two different instances with different passwords are not equal', + () { + expect( + const LoginPasswordChanged('password1'), + isNot(const LoginPasswordChanged('password2')), + ); + }); + }); + + group('LoginWithEmailAndPasswordRequested', () { + test('supports value comparison', () { + expect( + const LoginWithEmailAndPasswordRequested().props, + equals([]), + ); + expect( + const LoginWithEmailAndPasswordRequested(), + const LoginWithEmailAndPasswordRequested(), + ); + }); + }); + + group('LoginPasswordVisibilityChanged', () { + test('supports value comparison', () { + expect( + const LoginPasswordVisibilityChanged(obscure: true), + const LoginPasswordVisibilityChanged(obscure: true), + ); + }); + + test( + 'two different instances with different obscure values are not equal', + () { + expect( + const LoginPasswordVisibilityChanged(obscure: true), + isNot(const LoginPasswordVisibilityChanged(obscure: false)), + ); + }); + }); + }); +} diff --git a/todo/test/authentication/login/view/forgot_password_page_test.dart b/todo/test/authentication/login/view/forgot_password_page_test.dart new file mode 100644 index 0000000..a16095a --- /dev/null +++ b/todo/test/authentication/login/view/forgot_password_page_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('ForgotPasswordPage', () { + test('is a page', () { + expect(ForgotPasswordPage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = ForgotPasswordPage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [ForgotPasswordView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + ForgotPasswordPage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(ForgotPasswordView), findsOneWidget); + }); +} diff --git a/todo/test/authentication/login/view/login_view_test.dart b/todo/test/authentication/login/view/login_view_test.dart new file mode 100644 index 0000000..f021cd0 --- /dev/null +++ b/todo/test/authentication/login/view/login_view_test.dart @@ -0,0 +1,255 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/login/login.dart'; +import 'package:todo_ui/todo_ui.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late LoginBloc mockLoginBloc; + late NavigatorObserver mockNavigatorObserver; + + setUpAll(() { + registerFallbackValue(FakeRoute()); + }); + + setUp(() { + mockLoginBloc = MockLoginBloc(); + mockNavigatorObserver = MockNavigatorObserver(); + + when(() => mockLoginBloc.state).thenReturn(const LoginState.initial()); + }); + + group('LoginView', () { + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + expect(find.byType(TextField), findsNWidgets(2)); + expect(find.byType(UIOutlinedButton), findsOneWidget); + }); + + testWidgets('shows success snackbar when sign up is successful', + (WidgetTester tester) async { + whenListen( + mockLoginBloc, + Stream.fromIterable([ + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.success), + ]), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('shows error snackbar when sign up fails', + (WidgetTester tester) async { + whenListen( + mockLoginBloc, + Stream.fromIterable([ + const LoginState.initial() + .copyWith(status: FormzSubmissionStatus.failure), + ]), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('login button is disabled when form is invalid', + (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + await tester.pumpAndSettle(); + final loginButton = find.byType(OutlinedButton); + expect(tester.widget(loginButton).enabled, isFalse); + }); + + testWidgets('login button is enabled when form is valid', + (WidgetTester tester) async { + when(() => mockLoginBloc.state).thenReturn( + const LoginState.initial().copyWith( + email: const Email.dirty('email@mail.com'), + password: const Password.dirty('Test12345.'), + ), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final signUpButton = find.byType(OutlinedButton); + expect(tester.widget(signUpButton).enabled, isTrue); + }); + + testWidgets( + 'adds [LoginWithEmailAndPasswordRequested]' + ' when login button is pressed', (WidgetTester tester) async { + when(() => mockLoginBloc.state).thenReturn( + const LoginState.initial().copyWith( + email: const Email.dirty('email@mail.com'), + password: const Password.dirty('Test12345.'), + ), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final loginButton = find.byType(OutlinedButton); + await tester.tap(loginButton); + verify( + () => mockLoginBloc.add(const LoginWithEmailAndPasswordRequested()), + ).called(1); + }); + + testWidgets( + 'adds [LoginPasswordChanged] when password textfield is changed', + (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final passwordTextField = find.byKey( + const Key('LoginPasswordTextField'), + ); + await tester.enterText(passwordTextField, 'Test12345.'); + + verify( + () => mockLoginBloc.add( + const LoginPasswordChanged('Test12345.'), + ), + ).called(1); + }); + + testWidgets( + 'changes visibility when [PasswordTextField] icon ' + ' is tapped', (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final passwordTextField = find.byKey( + const Key('LoginPasswordTextField'), + ); + + await tester.tap( + find.descendant( + of: passwordTextField, + matching: find.byType(IconButton), + ), + ); + + verify( + () => mockLoginBloc.add( + const LoginPasswordVisibilityChanged(obscure: false), + ), + ).called(1); + }); + + testWidgets('[EmailTextField] should display error state', + (WidgetTester tester) async { + when(() => mockLoginBloc.state).thenReturn( + const LoginState.initial().copyWith( + email: const Email.dirty('asd'), + ), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final emailTextField = find + .byKey(const Key('LoginEmailTextField')) + .evaluate() + .where((element) => element.widget is UITextField) + .single; + + expect(emailTextField, isNotNull); + + await tester.pump(); + + final textFieldWidget = emailTextField.widget as UITextField; + expect(textFieldWidget.errorText, isNotNull); + }); + + testWidgets('[EmailTextField] should add [LoginEmailChanged] event', + (WidgetTester tester) async { + when(() => mockLoginBloc.state).thenReturn( + const LoginState.initial().copyWith( + email: const Email.dirty('asd'), + ), + ); + + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + ); + + final emailTextField = find.byKey( + const Key('LoginEmailTextField'), + ); + await tester.enterText(emailTextField, 'email'); + + verify( + () => mockLoginBloc.add( + const LoginEmailChanged('email'), + ), + ).called(1); + }); + + testWidgets('[ForgotPasswordButton] navigates on pressed', + (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + navigatorObserver: mockNavigatorObserver, + ); + + final forgotPasswordButton = find.byKey( + const Key('LoginForgotPasswordButton'), + ); + await tester.tap(forgotPasswordButton); + verify(() => mockNavigatorObserver.didPush(any(), any())); + }); + + testWidgets('[SignUpButton] navigates on pressed', + (WidgetTester tester) async { + await tester.pumpApp( + const LoginView(), + loginBloc: mockLoginBloc, + navigatorObserver: mockNavigatorObserver, + ); + + final signUpButton = find.byKey( + const Key('LoginSignUpButton'), + ); + await tester.tap(signUpButton); + verify(() => mockNavigatorObserver.didPush(any(), any())); + }); + }); +} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart new file mode 100644 index 0000000..d7cf135 --- /dev/null +++ b/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart @@ -0,0 +1,141 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:user_repository/user_repository.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late UserRepository mockUserRepository; + + setUp(() { + mockUserRepository = MockUserRepository(); + }); + + group('SignUpBloc', () { + blocTest( + 'emits [] when nothing is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + expect: () => [], + ); + + blocTest( + 'emits [name] when SignUpNameChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const SignUpNameChanged('name')), + expect: () => [ + const SignUpState.initial().copyWith(name: const Name.dirty('name')), + ], + ); + + blocTest( + 'emits [email] when SignUpEmailChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const SignUpEmailChanged('email')), + expect: () => [ + const SignUpState.initial().copyWith(email: const Email.dirty('email')), + ], + ); + + blocTest( + 'emits [password] when SignUpPasswordChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => + bloc.add(const SignUpPasswordChanged(password: 'password')), + expect: () => [ + const SignUpState.initial() + .copyWith(password: const Password.dirty('password')), + ], + ); + + blocTest( + 'emits [confirmationPassword] when ' + 'SignUpConfirmationPasswordChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add( + const SignUpConfirmationPasswordChanged( + password: 'confirmationPassword', + ), + ), + expect: () => [ + const SignUpState.initial().copyWith( + confirmationPassword: 'confirmationPassword', + ), + ], + ); + + blocTest( + 'emits [obscurePasswords] as true ' + 'when SignUpPasswordVisibilityChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => + bloc.add(const SignUpPasswordVisibilityChanged(obscure: true)), + expect: () => [ + const SignUpState.initial().copyWith( + obscurePasswords: true, + ), + ], + ); + + blocTest( + 'emits [obscurePasswords] as false ' + 'when SignUpPasswordVisibilityChanged is added', + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => + bloc.add(const SignUpPasswordVisibilityChanged(obscure: false)), + expect: () => [ + const SignUpState.initial().copyWith( + obscurePasswords: false, + ), + ], + ); + + blocTest( + 'emits [FormzSubmissionStatus.inProgress] ' + 'and [FormzSubmissionStatus.success] ' + 'when SignUpWithEmailAndPasswordRequested is added', + setUp: () { + when( + () => mockUserRepository.signUp( + email: any(named: 'email'), + password: any(named: 'password'), + name: any(named: 'name'), + ), + ).thenAnswer((_) async {}); + }, + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const SignUpWithEmailAndPasswordRequested()), + expect: () => [ + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.inProgress), + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.success), + ], + ); + + blocTest( + 'emits [FormzSubmissionStatus.inProgress] ' + 'and [FormzSubmissionStatus.failure] ' + 'when SignUpWithEmailAndPasswordRequested is added', + setUp: () { + when( + () => mockUserRepository.signUp( + email: any(named: 'email'), + password: any(named: 'password'), + name: any(named: 'name'), + ), + ).thenThrow(Exception()); + }, + build: () => SignUpBloc(userRepository: mockUserRepository), + act: (bloc) => bloc.add(const SignUpWithEmailAndPasswordRequested()), + expect: () => [ + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.inProgress), + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.failure), + ], + ); + }); +} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart new file mode 100644 index 0000000..fa25c74 --- /dev/null +++ b/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart @@ -0,0 +1,108 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; + +void main() { + const testEmail1 = 'test1@example.com'; + const testEmail2 = 'test2@example.com'; + const testPassword1 = 'password1'; + const testPassword2 = 'password2'; + const testName1 = 'John Doe'; + const testName2 = 'Jane Doe'; + + group('SignUpEvent', () { + group('SignUpEmailChanged', () { + test('supports value comparison', () { + expect( + const SignUpEmailChanged(testEmail1), + const SignUpEmailChanged(testEmail1), + ); + }); + + test('two different instances with different emails are not equal', () { + expect( + const SignUpEmailChanged(testEmail1), + isNot(const SignUpEmailChanged(testEmail2)), + ); + }); + }); + + group('SignUpPasswordChanged', () { + test('supports value comparison', () { + expect( + const SignUpPasswordChanged(password: testPassword1), + const SignUpPasswordChanged(password: testPassword1), + ); + }); + + test('two different instances with different passwords are not equal', + () { + expect( + const SignUpPasswordChanged(password: testPassword1), + isNot(const SignUpPasswordChanged(password: testPassword2)), + ); + }); + }); + + group('SignUpConfirmationPasswordChanged', () { + test('supports value comparison', () { + expect( + const SignUpConfirmationPasswordChanged(password: testPassword1), + const SignUpConfirmationPasswordChanged(password: testPassword1), + ); + }); + + test('two different instances with different passwords are not equal', + () { + expect( + const SignUpConfirmationPasswordChanged(password: testPassword1), + isNot( + const SignUpConfirmationPasswordChanged(password: testPassword2), + ), + ); + }); + }); + + group('SignUpWithEmailAndPasswordRequested', () { + test('supports value comparison', () { + expect( + const SignUpWithEmailAndPasswordRequested(), + const SignUpWithEmailAndPasswordRequested(), + ); + }); + }); + + group('SignUpPasswordVisibilityChanged', () { + test('supports value comparison', () { + expect( + const SignUpPasswordVisibilityChanged(obscure: true), + const SignUpPasswordVisibilityChanged(obscure: true), + ); + }); + + test( + 'two different instances with different obscure values are not equal', + () { + expect( + const SignUpPasswordVisibilityChanged(obscure: true), + isNot(const SignUpPasswordVisibilityChanged(obscure: false)), + ); + }); + }); + + group('SignUpNameChanged', () { + test('supports value comparison', () { + expect( + const SignUpNameChanged(testName1), + const SignUpNameChanged(testName1), + ); + }); + + test('two different instances with different names are not equal', () { + expect( + const SignUpNameChanged(testName1), + isNot(const SignUpNameChanged(testName2)), + ); + }); + }); + }); +} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart new file mode 100644 index 0000000..978572e --- /dev/null +++ b/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; + +void main() { + group('SignUpState', () { + const name = Name.dirty('John Doe'); + const invalidPassword = Password.dirty('password123'); + const validPassword = Password.dirty('Password123!'); + const validPasswordValue = 'Password123!'; + const email = Email.dirty('test@example.com'); + + test('supports value comparison', () { + const state1 = SignUpState.initial(); + const state2 = SignUpState.initial(); + + expect(state1, state2); + }); + + test('copyWith method creates new instance with replaced values', () { + const originalState = SignUpState.initial(); + final modifiedState = originalState.copyWith( + status: FormzSubmissionStatus.success, + email: email, + password: validPassword, + name: name, + confirmationPassword: validPassword.value, + obscurePasswords: false, + ); + + expect(modifiedState.status, FormzSubmissionStatus.success); + expect(modifiedState.email.value, email.value); + expect(modifiedState.password.value, validPassword.value); + expect(modifiedState.name.value, name.value); + expect(modifiedState.confirmationPassword, validPassword.value); + expect(modifiedState.obscurePasswords, false); + }); + + test('valid getter returns true when all fields are valid', () { + const state = SignUpState( + status: FormzSubmissionStatus.success, + email: email, + password: validPassword, + name: name, + confirmationPassword: validPasswordValue, + obscurePasswords: false, + ); + + expect(state.valid, true); + }); + + test('valid getter returns false when any field is invalid', () { + const state = SignUpState( + status: FormzSubmissionStatus.initial, + email: email, + password: invalidPassword, + name: Name.pure(), + confirmationPassword: validPasswordValue, + obscurePasswords: false, + ); + + expect(state.valid, false); + }); + + test('props list contains all state properties', () { + const state = SignUpState( + status: FormzSubmissionStatus.initial, + email: email, + password: validPassword, + name: name, + confirmationPassword: validPasswordValue, + obscurePasswords: false, + ); + + expect(state.props, [ + FormzSubmissionStatus.initial, + email, + validPassword, + validPasswordValue, + false, + name, + ]); + }); + }); +} diff --git a/todo/test/authentication/sign_up/view/sign_up_page_test.dart b/todo/test/authentication/sign_up/view/sign_up_page_test.dart new file mode 100644 index 0000000..636a57e --- /dev/null +++ b/todo/test/authentication/sign_up/view/sign_up_page_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/authentication/sign_up/view/view.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group('SignUpPage', () { + test('is a page', () { + expect(const SignUpPage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = const SignUpPage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [SignUpView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + const SignUpPage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(SignUpView), findsOneWidget); + }); +} diff --git a/todo/test/authentication/sign_up/view/sign_up_view_test.dart b/todo/test/authentication/sign_up/view/sign_up_view_test.dart new file mode 100644 index 0000000..80ef53d --- /dev/null +++ b/todo/test/authentication/sign_up/view/sign_up_view_test.dart @@ -0,0 +1,399 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_inputs/form_inputs.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:go_router/go_router.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + late SignUpBloc mockSignUpBloc; + late NavigatorObserver mockNavigatorObserver; + + setUpAll(() { + registerFallbackValue(FakeRoute()); + }); + + setUp(() { + mockSignUpBloc = MockSignUpBloc(); + mockNavigatorObserver = MockNavigatorObserver(); + when(() => mockSignUpBloc.state).thenReturn(const SignUpState.initial()); + }); + + group('SignUpView', () { + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + expect(find.byType(TextField), findsNWidgets(4)); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('shows success snackbar when sign up is successful', + (WidgetTester tester) async { + whenListen( + mockSignUpBloc, + Stream.fromIterable([ + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.success), + ]), + ); + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('pops screen when sign up is successful', + (WidgetTester tester) async { + whenListen( + mockSignUpBloc, + Stream.fromIterable([ + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.success), + ]), + ); + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => context.push( + '/signUpTestPath', + ), + ), + ); + }, + ), + signUpBloc: mockSignUpBloc, + navigatorObserver: mockNavigatorObserver, + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + + verify(() => mockNavigatorObserver.didPop(any(), any())); + }); + + testWidgets('shows error snackbar when sign up fails', + (WidgetTester tester) async { + whenListen( + mockSignUpBloc, + Stream.fromIterable([ + const SignUpState.initial() + .copyWith(status: FormzSubmissionStatus.failure), + ]), + ); + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + await tester.pumpAndSettle(); + expect(find.byType(ScaffoldMessenger), findsOneWidget); + }); + + testWidgets('signUp button is disabled when form is invalid', + (WidgetTester tester) async { + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + await tester.pumpAndSettle(); + final signUpButton = find.byType(ElevatedButton); + expect(tester.widget(signUpButton).enabled, isFalse); + }); + + testWidgets('signUp button is enabled when form is valid', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + email: const Email.dirty('email@mail.com'), + password: const Password.dirty('a123456789P.'), + confirmationPassword: 'a123456789P.', + name: const Name.dirty('name'), + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final signUpButton = find.byType(ElevatedButton); + expect(tester.widget(signUpButton).enabled, isTrue); + }); + + testWidgets('signUp button onPressed is called when pressed', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + email: const Email.dirty('email@mail.com'), + password: const Password.dirty('a123456789P.'), + confirmationPassword: 'a123456789P.', + name: const Name.dirty('name'), + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final signUpButton = find.byType(ElevatedButton); + await tester.tap(signUpButton); + verify( + () => mockSignUpBloc.add( + const SignUpWithEmailAndPasswordRequested(), + ), + ).called(1); + }); + + testWidgets('should change [NameTextField] value on change', + (WidgetTester tester) async { + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final nameTextFieldFinder = find.byKey( + const Key('SignUpNameTextField'), + ); + await tester.enterText(nameTextFieldFinder, 'name'); + expect(find.text('name'), findsOneWidget); + }); + + testWidgets('[NameTextField] should display error state', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + name: const Name.dirty('@#%^&*'), + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final nameTextFieldFinder = find.byKey( + const Key('SignUpNameTextField'), + ); + expect( + tester.widget(nameTextFieldFinder).decoration?.errorText, + isNotNull, + ); + }); + + testWidgets('should change [EmailTextField] value on change', + (WidgetTester tester) async { + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final emailTextFieldFinder = find.byKey( + const Key('SignUpEmailTextField'), + ); + await tester.enterText( + emailTextFieldFinder, + 'email@email.com', + ); + + expect(find.text('email@email.com'), findsOneWidget); + }); + + testWidgets('[EmailTextField] should display error state', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + email: const Email.dirty('email'), + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final emailTextFieldFinder = find.byKey( + const Key('SignUpEmailTextField'), + ); + expect( + tester.widget(emailTextFieldFinder).decoration?.errorText, + isNotNull, + ); + }); + + testWidgets('should change [PasswordTextField] value on change', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + obscurePasswords: false, + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final passwordTextFieldFinder = find.byKey( + const Key('SignUpPasswordTextField'), + ); + await tester.enterText( + passwordTextFieldFinder, + 'a123456789P.', + ); + verify( + () => mockSignUpBloc.add( + const SignUpPasswordChanged( + password: 'a123456789P.', + ), + ), + ).called(1); + }); + + testWidgets('[PasswordTextField] should display error state', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + password: const Password.dirty('password'), + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final passwordTextFieldFinder = find.byKey( + const Key('SignUpPasswordTextField'), + ); + expect( + tester.widget(passwordTextFieldFinder).decoration?.errorText, + isNotNull, + ); + }); + + testWidgets('should change [ConfirmationPasswordTextField] value on change', + (WidgetTester tester) async { + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final confirmationPasswordTextFieldFinder = find.byKey( + const Key('SignUpConfirmationPasswordTextField'), + ); + await tester.enterText( + confirmationPasswordTextFieldFinder, + 'a123456789P.', + ); + verify( + () => mockSignUpBloc.add( + const SignUpConfirmationPasswordChanged( + password: 'a123456789P.', + ), + ), + ).called(1); + }); + + testWidgets('[ConfirmationPasswordTextField] should display error state', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + password: const Password.dirty('password1234'), + confirmationPassword: 'password', + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final confirmationPasswordTextFieldFinder = find.byKey( + const Key('SignUpConfirmationPasswordTextField'), + ); + + expect( + tester + .widget(confirmationPasswordTextFieldFinder) + .decoration + ?.errorText, + isNotNull, + ); + }); + + testWidgets('[ConfirmationPasswordTextField] changes visibility', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + obscurePasswords: false, + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final confirmationPasswordTextFieldFinder = find.byKey( + const Key('SignUpConfirmationPasswordTextField'), + ); + + await tester.tap( + find.descendant( + of: confirmationPasswordTextFieldFinder, + matching: find.byType(IconButton), + ), + ); + + verify( + () => mockSignUpBloc.add( + const SignUpPasswordVisibilityChanged(obscure: true), + ), + ).called(1); + }); + + testWidgets('[PasswordTextField] changes visibility', + (WidgetTester tester) async { + when(() => mockSignUpBloc.state).thenReturn( + const SignUpState.initial().copyWith( + obscurePasswords: false, + ), + ); + + await tester.pumpApp( + const SignUpView(), + signUpBloc: mockSignUpBloc, + ); + + final passwordTextFieldFinder = find.byKey( + const Key('SignUpPasswordTextField'), + ); + + await tester.tap( + find.descendant( + of: passwordTextFieldFinder, + matching: find.byType(IconButton), + ), + ); + + verify( + () => mockSignUpBloc.add( + const SignUpPasswordVisibilityChanged(obscure: true), + ), + ).called(1); + }); + }); +} diff --git a/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart new file mode 100644 index 0000000..d8d2a5c --- /dev/null +++ b/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; + +import '../helpers/helpers.dart'; + +void main() { + group('DownForMaintenancePage', () { + test('is a page', () { + expect(const DownForMaintenancePage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = + const DownForMaintenancePage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [DownForMaintenanceView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + const DownForMaintenancePage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(DownForMaintenanceView), findsOneWidget); + }); +} diff --git a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart new file mode 100644 index 0000000..dc4d880 --- /dev/null +++ b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/down_for_maintenance/down_for_maintenance_view.dart'; +import 'package:todo/l10n/l10n.dart'; + +void main() { + group('DownForMaintenanceView', () { + testWidgets('renders correct text', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: DownForMaintenanceView(), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), + ); + + expect(find.byType(Text), findsOneWidget); + }); + + testWidgets('showAsDialog displays dialog', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + DownForMaintenanceView.showAsDialog(context); + }, + child: const Text('Show Dialog'), + ); + }, + ), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.byType(Dialog), findsOneWidget); + }); + }); +} diff --git a/todo/test/force_upgrade/force_upgrade_page_test.dart b/todo/test/force_upgrade/force_upgrade_page_test.dart new file mode 100644 index 0000000..90aba63 --- /dev/null +++ b/todo/test/force_upgrade/force_upgrade_page_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/force_upgrade/force_upgrade.dart'; + +import '../helpers/helpers.dart'; + +void main() { + group('ForceUpgradePage', () { + test('is a page', () { + expect(const ForceUpgradePage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = const ForceUpgradePage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [ForceUpgradeView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + const ForceUpgradePage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(ForceUpgradeView), findsOneWidget); + }); +} diff --git a/todo/test/force_upgrade/force_upgrade_view_test.dart b/todo/test/force_upgrade/force_upgrade_view_test.dart new file mode 100644 index 0000000..5465d08 --- /dev/null +++ b/todo/test/force_upgrade/force_upgrade_view_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/force_upgrade/force_upgrade.dart'; +import 'package:todo/l10n/l10n.dart'; + +void main() { + group('ForceUpgradeView test', () { + testWidgets('renders correct text', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: ForceUpgradeView(), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), + ); + + expect(find.byType(Text), findsOneWidget); + }); + + testWidgets('showAsDialog displays dialog', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + ForceUpgradeView.showAsDialog(context); + }, + child: const Text('Show Dialog'), + ); + }, + ), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + expect(find.byType(Dialog), findsOneWidget); + }); + + testWidgets('renders dialog with Ok button', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + ForceUpgradeView.showAsDialog(context); + }, + child: const Text('Show Dialog'), + ); + }, + ), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + ), + ); + + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Ok')); + await tester.pump(); + }); + }); +} diff --git a/todo/test/helpers/helpers.dart b/todo/test/helpers/helpers.dart new file mode 100644 index 0000000..b15fe65 --- /dev/null +++ b/todo/test/helpers/helpers.dart @@ -0,0 +1 @@ +export 'pump_app.dart'; diff --git a/todo/test/helpers/pump_app.dart b/todo/test/helpers/pump_app.dart new file mode 100644 index 0000000..391c281 --- /dev/null +++ b/todo/test/helpers/pump_app.dart @@ -0,0 +1,137 @@ +import 'package:api_client/api_client.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/app/app.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo/authentication/forgot_password/forgot_password.dart'; +import 'package:todo/authentication/login/login.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:todo/home/home.dart'; +import 'package:todo/settings/bloc/settings_bloc.dart'; +import 'package:user_repository/user_repository.dart'; + +class MockAppBloc extends MockBloc implements AppBloc {} + +class MockLoginBloc extends MockBloc + implements LoginBloc {} + +class MockSignUpBloc extends MockBloc + implements SignUpBloc {} + +class MockDeleteAccountBloc + extends MockBloc + implements DeleteAccountBloc {} + +class MockForgotPasswordBloc + extends MockBloc + implements ForgotPasswordBloc {} + +class MockUserRepository extends Mock implements UserRepository {} + +class MockRouterConfig extends Mock implements GoRouter {} + +class MockBuildContext extends Mock implements BuildContext {} + +class MockNavigatorObserver extends Mock implements NavigatorObserver {} + +class MockApiClient extends Mock implements ApiClient {} + +class FakeRoute extends Fake implements Route {} + +class MockHomeBloc extends MockBloc implements HomeBloc {} + +class MockSettingsBloc extends MockBloc + implements SettingsBloc {} + +extension AppTester on WidgetTester { + Future pumpApp( + Widget widgetUnderTest, { + AppBloc? appBloc, + SignUpBloc? signUpBloc, + LoginBloc? loginBloc, + ForgotPasswordBloc? forgotPasswordBloc, + DeleteAccountBloc? deleteAccountBloc, + TargetPlatform? platform, + NavigatorObserver? navigatorObserver, + UserRepository? userRepository, + HomeBloc? homeBloc, + SettingsBloc? settingsBloc, + }) async { + final router = GoRouter( + observers: navigatorObserver == null ? [] : [navigatorObserver], + routes: [ + GoRoute( + path: '/', + builder: (context, state) => Scaffold( + body: Builder( + builder: (context) { + return widgetUnderTest; + }, + ), + ), + ), + GoRoute( + path: '/signUpTestPath', + builder: (context, state) => Scaffold( + body: Builder( + builder: (context) { + return const SignUpView(); + }, + ), + ), + ), + ], + ); + await pumpWidget( + MultiRepositoryProvider( + providers: [ + RepositoryProvider.value( + value: userRepository ?? MockUserRepository(), + ), + ], + child: MultiBlocProvider( + providers: [ + BlocProvider.value( + value: signUpBloc ?? MockSignUpBloc(), + ), + BlocProvider.value( + value: appBloc ?? MockAppBloc(), + ), + BlocProvider.value( + value: loginBloc ?? MockLoginBloc(), + ), + BlocProvider.value( + value: forgotPasswordBloc ?? MockForgotPasswordBloc(), + ), + BlocProvider.value( + value: homeBloc ?? MockHomeBloc(), + ), + BlocProvider.value( + value: settingsBloc ?? MockSettingsBloc(), + ), + BlocProvider.value( + value: deleteAccountBloc ?? MockDeleteAccountBloc(), + ), + ], + child: MaterialApp.router( + title: 'Somnio Starter', + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + routerConfig: router, + ), + ), + ), + ); + await pump(); + } +} diff --git a/todo/test/home/bloc/home_bloc_test.dart b/todo/test/home/bloc/home_bloc_test.dart new file mode 100644 index 0000000..54a662b --- /dev/null +++ b/todo/test/home/bloc/home_bloc_test.dart @@ -0,0 +1,13 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/home/home.dart'; + +void main() { + group('HomeBloc', () { + blocTest( + 'emits [] when nothing is added', + build: HomeBloc.new, + expect: () => [], + ); + }); +} diff --git a/todo/test/home/bloc/home_event_test.dart b/todo/test/home/bloc/home_event_test.dart new file mode 100644 index 0000000..b0288d5 --- /dev/null +++ b/todo/test/home/bloc/home_event_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/home/home.dart'; + +void main() { + test('supports value comparison', () { + expect(const HomeEvent().props, equals([])); + expect( + const HomeEvent(), + const HomeEvent(), + ); + }); +} diff --git a/todo/test/home/bloc/home_state_test.dart b/todo/test/home/bloc/home_state_test.dart new file mode 100644 index 0000000..e52c4fa --- /dev/null +++ b/todo/test/home/bloc/home_state_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/home/home.dart'; + +void main() { + test('supports value comparison', () { + expect( + const HomeState.initial().props, + equals([const HomeState.initial().status, 0]), + ); + expect( + const HomeState.initial(), + const HomeState.initial(), + ); + }); + + test('returns a copy with updated status', () { + expect( + const HomeState.initial().copyWith(status: HomeStatus.loading), + const HomeState(status: HomeStatus.loading), + ); + expect(const HomeState.initial().copyWith(), const HomeState.initial()); + }); +} diff --git a/todo/test/home/view/home_page_test.dart b/todo/test/home/view/home_page_test.dart new file mode 100644 index 0000000..b6636be --- /dev/null +++ b/todo/test/home/view/home_page_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/home/home.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('HomePage', () { + test('is a page', () { + expect(const HomePage(), isA>()); + }); + }); + + test('route is a [MaterialPageRoute]', () { + final route = const HomePage().createRoute(MockBuildContext()); + expect(route, isA>()); + }); + + testWidgets('route navigates to [HomeView]', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return SizedBox( + child: ElevatedButton( + child: const Text('Navigate'), + onPressed: () => Navigator.of(context).push( + const HomePage().createRoute(context), + ), + ), + ); + }, + ), + ); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(HomeView), findsOneWidget); + }); +} diff --git a/todo/test/home/view/home_view_test.dart b/todo/test/home/view/home_view_test.dart new file mode 100644 index 0000000..8ecb946 --- /dev/null +++ b/todo/test/home/view/home_view_test.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/home/home.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late NavigatorObserver mockNavigatorObserver; + late HomeBloc mockHomeBloc; + + setUpAll(() { + registerFallbackValue(FakeRoute()); + mockHomeBloc = MockHomeBloc(); + when(() => mockHomeBloc.state).thenReturn(const HomeState.initial()); + }); + + setUp(() { + mockNavigatorObserver = MockNavigatorObserver(); + }); + group('HomeView', () { + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpApp( + const HomeView(), + homeBloc: mockHomeBloc, + ); + + expect( + find.byKey( + const Key('HomeAppBar'), + ), + findsOneWidget); + expect(find.byType(Placeholder), findsOneWidget); + }); + + testWidgets('navigates to [DeleteAccountView] when delete button is tapped', + (WidgetTester tester) async { + await tester.pumpApp( + const HomeView(), + navigatorObserver: mockNavigatorObserver, + homeBloc: mockHomeBloc, + ); + + await tester.tap(find.byKey( + const Key('HomeDeleteAccountButton'), + )); + await tester.pumpAndSettle(); + verify(() => mockNavigatorObserver.didPush(any(), any())); + }); + + testWidgets('navigates to bottombar [Logout]', (WidgetTester tester) async { + await tester.pumpApp( + const HomeView(), + navigatorObserver: mockNavigatorObserver, + homeBloc: mockHomeBloc, + ); + + await tester.tap(find.byKey(const Key('HomeLogoutButton'))); + await tester.pumpAndSettle(); + verify(() => mockNavigatorObserver.didPush(any(), any())); + }); + + testWidgets('dialog shows when button is pressed', + (WidgetTester tester) async { + await tester.pumpApp( + const HomeView(), + homeBloc: mockHomeBloc, + ); + final button = find.byKey( + const Key('HomeLogoutButton'), + ); + expect(button, findsOneWidget); + await tester.runAsync(() async { + final response = await tester.press(button); + await response.up(); + }); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + }); + + testWidgets('dialog dismisses when cancel button is pressed', + (WidgetTester tester) async { + await tester.pumpApp( + const HomeView(), + homeBloc: mockHomeBloc, + ); + final button = find.byKey( + const Key('HomeLogoutButton'), + ); + expect(button, findsOneWidget); + await tester.runAsync(() async { + final response = await tester.press(button); + await response.up(); + }); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + final cancelButton = find.byKey( + const Key('CancelButton'), + ); + await tester.tap(cancelButton); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + }); + }); +} diff --git a/todo/test/settings/bloc/settings_bloc_test.dart b/todo/test/settings/bloc/settings_bloc_test.dart new file mode 100644 index 0000000..3db95e5 --- /dev/null +++ b/todo/test/settings/bloc/settings_bloc_test.dart @@ -0,0 +1,26 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/settings/settings.dart'; + +void main() { + late SettingsBloc settingsBloc; + + setUp(() { + settingsBloc = SettingsBloc(); + }); + + tearDown(() { + settingsBloc.close(); + }); + + group('TermsAndConditionsRetrieved event', () { + blocTest( + 'emits error state when canLaunchUrl returns false', + build: SettingsBloc.new, + act: (bloc) => bloc.add(const TermsAndConditionsRetrieved()), + expect: () => [ + const SettingsState.initial().copyWith(status: SettingsStatus.error), + ], + ); + }); +} diff --git a/todo/test/settings/bloc/settings_event_test.dart b/todo/test/settings/bloc/settings_event_test.dart new file mode 100644 index 0000000..e0e0748 --- /dev/null +++ b/todo/test/settings/bloc/settings_event_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/settings/settings.dart'; + +void main() { + group('VersionNumberRetrieved', () { + test('props should be empty', () { + expect(const VersionNumberRetrieved().props, []); + }); + }); + + group('TermsAndConditionsRetrieved', () { + test('props should be empty', () { + expect(const TermsAndConditionsRetrieved().props, []); + }); + }); + + group('AboutUsRetrieved', () { + test('props should be empty', () { + expect(const AboutUsRetrieved().props, []); + }); + }); +} diff --git a/todo/test/settings/bloc/settings_state_test.dart b/todo/test/settings/bloc/settings_state_test.dart new file mode 100644 index 0000000..e054e2c --- /dev/null +++ b/todo/test/settings/bloc/settings_state_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todo/settings/bloc/settings_bloc.dart'; + +void main() { + test('supports value comparison', () { + expect( + const SettingsState.initial().props, + equals([const SettingsState.initial().status, '']), + ); + expect( + const SettingsState.initial(), + const SettingsState.initial(), + ); + }); + + test('returns a copy with updated status', () { + expect( + const SettingsState.initial().copyWith( + status: SettingsStatus.loading, + appVersion: '1.0.0+1', + ), + const SettingsState( + status: SettingsStatus.loading, + appVersion: '1.0.0+1', + ), + ); + expect( + const SettingsState.initial().copyWith(), + const SettingsState.initial(), + ); + }); +} diff --git a/todo/test/settings/view/settings_view_test.dart b/todo/test/settings/view/settings_view_test.dart new file mode 100644 index 0000000..1b6d65f --- /dev/null +++ b/todo/test/settings/view/settings_view_test.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:todo/settings/settings.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late NavigatorObserver mockNavigatorObserver; + late SettingsBloc mockSettingsBloc; + + setUpAll(() { + registerFallbackValue(FakeRoute()); + mockSettingsBloc = MockSettingsBloc(); + when(() => mockSettingsBloc.state) + .thenReturn(const SettingsState.initial()); + }); + + setUp(() { + mockNavigatorObserver = MockNavigatorObserver(); + }); + + group('SettingsView', () { + testWidgets('renders SettingsView', (tester) async { + await tester.pumpApp( + const SettingsView(), + settingsBloc: mockSettingsBloc, + ); + + expect( + find.byKey( + const Key('SettingsAppBar'), + ), + findsOneWidget, + ); + }); + + testWidgets('renders ContactUsSettingsItem', (tester) async { + await tester.pumpApp( + const SettingsView(), + settingsBloc: mockSettingsBloc, + ); + + expect( + find.byKey( + const Key('ContactUsSettingsItem'), + ), + findsOneWidget, + ); + }); + + testWidgets('renders TermsAndConditionsSettingsItem', (tester) async { + await tester.pumpApp( + const SettingsView(), + settingsBloc: mockSettingsBloc, + ); + + expect( + find.byKey( + const Key('TermsAndConditionsSettingsItem'), + ), + findsOneWidget, + ); + }); + + testWidgets('renders AboutUsSettingsItem', (tester) async { + await tester.pumpApp( + const SettingsView(), + settingsBloc: mockSettingsBloc, + ); + + expect( + find.byKey( + const Key('AboutUsSettingsItem'), + ), + findsOneWidget, + ); + }); + + testWidgets('renders AppVersionSettingsItem', (tester) async { + await tester.pumpApp( + const SettingsView(), + settingsBloc: mockSettingsBloc, + ); + + expect( + find.byKey( + const Key('AppVersionSettingsItem'), + ), + findsOneWidget, + ); + }); + + testWidgets('navigates to DeleteAccountPage when delete account is tapped', + (tester) async { + await tester.pumpApp( + const SettingsView(), + navigatorObserver: mockNavigatorObserver, + settingsBloc: mockSettingsBloc, + ); + + await tester.tap(find.byKey(const Key('DeleteAccountSettingsItem'))); + await tester.pumpAndSettle(); + verify(() => mockNavigatorObserver.didPush(any(), any())); + }); + }); +} diff --git a/todo/web/favicon.png b/todo/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/todo/web/icons/Icon-192.png b/todo/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/todo/web/icons/Icon-512.png b/todo/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 zcmd5=2T+s!lYZ%-(h(2@5fr2dC?F^$C=i-}R6$UX8af(!je;W5yC_|HmujSgN*6?W z3knF*TL1$|?oD*=zPbBVex*RUIKsL<(&Rj9%^UD2IK3W?2j>D?eWQgvS-HLymHo9%~|N2Q{~j za?*X-{b9JRowv_*Mh|;*-kPFn>PI;r<#kFaxFqbn?aq|PduQg=2Q;~Qc}#z)_T%x9 zE|0!a70`58wjREmAH38H1)#gof)U3g9FZ^ zF7&-0^Hy{4XHWLoC*hOG(dg~2g6&?-wqcpf{ z&3=o8vw7lMi22jCG9RQbv8H}`+}9^zSk`nlR8?Z&G2dlDy$4#+WOlg;VHqzuE=fM@ z?OI6HEJH4&tA?FVG}9>jAnq_^tlw8NbjNhfqk2rQr?h(F&WiKy03Sn=-;ZJRh~JrD zbt)zLbnabttEZ>zUiu`N*u4sfQaLE8-WDn@tHp50uD(^r-}UsUUu)`!Rl1PozAc!a z?uj|2QDQ%oV-jxUJmJycySBINSKdX{kDYRS=+`HgR2GO19fg&lZKyBFbbXhQV~v~L za^U944F1_GtuFXtvDdDNDvp<`fqy);>Vw=ncy!NB85Tw{&sT5&Ox%-p%8fTS;OzlRBwErvO+ROe?{%q-Zge=%Up|D4L#>4K@Ke=x%?*^_^P*KD zgXueMiS63!sEw@fNLB-i^F|@Oib+S4bcy{eu&e}Xvb^(mA!=U=Xr3||IpV~3K zQWzEsUeX_qBe6fky#M zzOJm5b+l;~>=sdp%i}}0h zO?B?i*W;Ndn02Y0GUUPxERG`3Bjtj!NroLoYtyVdLtl?SE*CYpf4|_${ku2s`*_)k zN=a}V8_2R5QANlxsq!1BkT6$4>9=-Ix4As@FSS;1q^#TXPrBsw>hJ}$jZ{kUHoP+H zvoYiR39gX}2OHIBYCa~6ERRPJ#V}RIIZakUmuIoLF*{sO8rAUEB9|+A#C|@kw5>u0 zBd=F!4I)Be8ycH*)X1-VPiZ+Ts8_GB;YW&ZFFUo|Sw|x~ZajLsp+_3gv((Q#N>?Jz zFBf`~p_#^${zhPIIJY~yo!7$-xi2LK%3&RkFg}Ax)3+dFCjGgKv^1;lUzQlPo^E{K zmCnrwJ)NuSaJEmueEPO@(_6h3f5mFffhkU9r8A8(JC5eOkux{gPmx_$Uv&|hyj)gN zd>JP8l2U&81@1Hc>#*su2xd{)T`Yw< zN$dSLUN}dfx)Fu`NcY}TuZ)SdviT{JHaiYgP4~@`x{&h*Hd>c3K_To9BnQi@;tuoL z%PYQo&{|IsM)_>BrF1oB~+`2_uZQ48z9!)mtUR zdfKE+b*w8cPu;F6RYJiYyV;PRBbThqHBEu_(U{(gGtjM}Zi$pL8Whx}<JwE3RM0F8x7%!!s)UJVq|TVd#hf1zVLya$;mYp(^oZQ2>=ZXU1c$}f zm|7kfk>=4KoQoQ!2&SOW5|JP1)%#55C$M(u4%SP~tHa&M+=;YsW=v(Old9L3(j)`u z2?#fK&1vtS?G6aOt@E`gZ9*qCmyvc>Ma@Q8^I4y~f3gs7*d=ATlP>1S zyF=k&6p2;7dn^8?+!wZO5r~B+;@KXFEn^&C=6ma1J7Au6y29iMIxd7#iW%=iUzq&C=$aPLa^Q zncia$@TIy6UT@69=nbty5epP>*fVW@5qbUcb2~Gg75dNd{COFLdiz3}kODn^U*=@E z0*$7u7Rl2u)=%fk4m8EK1ctR!6%Ve`e!O20L$0LkM#f+)n9h^dn{n`T*^~d+l*Qlx z$;JC0P9+en2Wlxjwq#z^a6pdnD6fJM!GV7_%8%c)kc5LZs_G^qvw)&J#6WSp< zmsd~1-(GrgjC56Pdf6#!dt^y8Rg}!#UXf)W%~PeU+kU`FeSZHk)%sFv++#Dujk-~m zFHvVJC}UBn2jN& zs!@nZ?e(iyZPNo`p1i#~wsv9l@#Z|ag3JR>0#u1iW9M1RK1iF6-RbJ4KYg?B`dET9 zyR~DjZ>%_vWYm*Z9_+^~hJ_|SNTzBKx=U0l9 z9x(J96b{`R)UVQ$I`wTJ@$_}`)_DyUNOso6=WOmQKI1e`oyYy1C&%AQU<0-`(ow)1 zT}gYdwWdm4wW6|K)LcfMe&psE0XGhMy&xS`@vLi|1#Za{D6l@#D!?nW87wcscUZgELT{Cz**^;Zb~7 z(~WFRO`~!WvyZAW-8v!6n&j*PLm9NlN}BuUN}@E^TX*4Or#dMMF?V9KBeLSiLO4?B zcE3WNIa-H{ThrlCoN=XjOGk1dT=xwwrmt<1a)mrRzg{35`@C!T?&_;Q4Ce=5=>z^*zE_c(0*vWo2_#TD<2)pLXV$FlwP}Ik74IdDQU@yhkCr5h zn5aa>B7PWy5NQ!vf7@p_qtC*{dZ8zLS;JetPkHi>IvPjtJ#ThGQD|Lq#@vE2xdl%`x4A8xOln}BiQ92Po zW;0%A?I5CQ_O`@Ad=`2BLPPbBuPUp@Hb%a_OOI}y{Rwa<#h z5^6M}s7VzE)2&I*33pA>e71d78QpF>sNK;?lj^Kl#wU7G++`N_oL4QPd-iPqBhhs| z(uVM}$ItF-onXuuXO}o$t)emBO3Hjfyil@*+GF;9j?`&67GBM;TGkLHi>@)rkS4Nj zAEk;u)`jc4C$qN6WV2dVd#q}2X6nKt&X*}I@jP%Srs%%DS92lpDY^K*Sx4`l;aql$ zt*-V{U&$DM>pdO?%jt$t=vg5|p+Rw?SPaLW zB6nvZ69$ne4Z(s$3=Rf&RX8L9PWMV*S0@R zuIk&ba#s6sxVZ51^4Kon46X^9`?DC9mEhWB3f+o4#2EXFqy0(UTc>GU| zGCJmI|Dn-dX#7|_6(fT)>&YQ0H&&JX3cTvAq(a@ydM4>5Njnuere{J8p;3?1az60* z$1E7Yyxt^ytULeokgDnRVKQw9vzHg1>X@@jM$n$HBlveIrKP5-GJq%iWH#odVwV6cF^kKX(@#%%uQVb>#T6L^mC@)%SMd4DF? zVky!~ge27>cpUP1Vi}Z32lbLV+CQy+T5Wdmva6Fg^lKb!zrg|HPU=5Qu}k;4GVH+x z%;&pN1LOce0w@9i1Mo-Y|7|z}fbch@BPp2{&R-5{GLoeu8@limQmFF zaJRR|^;kW_nw~0V^ zfTnR!Ni*;-%oSHG1yItARs~uxra|O?YJxBzLjpeE-=~TO3Dn`JL5Gz;F~O1u3|FE- zvK2Vve`ylc`a}G`gpHg58Cqc9fMoy1L}7x7T>%~b&irrNMo?np3`q;d3d;zTK>nrK zOjPS{@&74-fA7j)8uT9~*g23uGnxwIVj9HorzUX#s0pcp2?GH6i}~+kv9fWChtPa_ z@T3m+$0pbjdQw7jcnHn;Pi85hk_u2-1^}c)LNvjdam8K-XJ+KgKQ%!?2n_!#{$H|| zLO=%;hRo6EDmnOBKCL9Cg~ETU##@u^W_5joZ%Et%X_n##%JDOcsO=0VL|Lkk!VdRJ z^|~2pB@PUspT?NOeO?=0Vb+fAGc!j%Ufn-cB`s2A~W{Zj{`wqWq_-w0wr@6VrM zbzni@8c>WS!7c&|ZR$cQ;`niRw{4kG#e z70e!uX8VmP23SuJ*)#(&R=;SxGAvq|&>geL&!5Z7@0Z(No*W561n#u$Uc`f9pD70# z=sKOSK|bF~#khTTn)B28h^a1{;>EaRnHj~>i=Fnr3+Fa4 z`^+O5_itS#7kPd20rq66_wH`%?HNzWk@XFK0n;Z@Cx{kx==2L22zWH$Yg?7 zvDj|u{{+NR3JvUH({;b*$b(U5U z7(lF!1bz2%06+|-v(D?2KgwNw7( zJB#Tz+ZRi&U$i?f34m7>uTzO#+E5cbaiQ&L}UxyOQq~afbNB4EI{E04ZWg53w0A{O%qo=lF8d zf~ktGvIgf-a~zQoWf>loF7pOodrd0a2|BzwwPDV}ShauTK8*fmF6NRbO>Iw9zZU}u zw8Ya}?seBnEGQDmH#XpUUkj}N49tP<2jYwTFp!P+&Fd(%Z#yo80|5@zN(D{_pNow*&4%ql zW~&yp@scb-+Qj-EmErY+Tu=dUmf@*BoXY2&oKT8U?8?s1d}4a`Aq>7SV800m$FE~? zjmz(LY+Xx9sDX$;vU`xgw*jLw7dWOnWWCO8o|;}f>cu0Q&`0I{YudMn;P;L3R-uz# zfns_mZED_IakFBPP2r_S8XM$X)@O-xVKi4`7373Jkd5{2$M#%cRhWer3M(vr{S6>h zj{givZJ3(`yFL@``(afn&~iNx@B1|-qfYiZu?-_&Z8+R~v`d6R-}EX9IVXWO-!hL5 z*k6T#^2zAXdardU3Ao~I)4DGdAv2bx{4nOK`20rJo>rmk3S2ZDu}))8Z1m}CKigf0 z3L`3Y`{huj`xj9@`$xTZzZc3je?n^yG<8sw$`Y%}9mUsjUR%T!?k^(q)6FH6Af^b6 zlPg~IEwg0y;`t9y;#D+uz!oE4VP&Je!<#q*F?m5L5?J3i@!0J6q#eu z!RRU`-)HeqGi_UJZ(n~|PSNsv+Wgl{P-TvaUQ9j?ZCtvb^37U$sFpBrkT{7Jpd?HpIvj2!}RIq zH{9~+gErN2+}J`>Jvng2hwM`=PLNkc7pkjblKW|+Fk9rc)G1R>Ww>RC=r-|!m-u7( zc(a$9NG}w#PjWNMS~)o=i~WA&4L(YIW25@AL9+H9!?3Y}sv#MOdY{bb9j>p`{?O(P zIvb`n?_(gP2w3P#&91JX*md+bBEr%xUHMVqfB;(f?OPtMnAZ#rm5q5mh;a2f_si2_ z3oXWB?{NF(JtkAn6F(O{z@b76OIqMC$&oJ_&S|YbFJ*)3qVX_uNf5b8(!vGX19hsG z(OP>RmZp29KH9Ge2kKjKigUmOe^K_!UXP`von)PR8Qz$%=EmOB9xS(ZxE_tnyzo}7 z=6~$~9k0M~v}`w={AeqF?_)9q{m8K#6M{a&(;u;O41j)I$^T?lx5(zlebpY@NT&#N zR+1bB)-1-xj}R8uwqwf=iP1GbxBjneCC%UrSdSxK1vM^i9;bUkS#iRZw2H>rS<2<$ zNT3|sDH>{tXb=zq7XZi*K?#Zsa1h1{h5!Tq_YbKFm_*=A5-<~j63he;4`77!|LBlo zR^~tR3yxcU=gDFbshyF6>o0bdp$qmHS7D}m3;^QZq9kBBU|9$N-~oU?G5;jyFR7>z hN`IR97YZXIo@y!QgFWddJ3|0`sjFx!m))><{BI=FK%f8s literal 0 HcmV?d00001 diff --git a/todo/web/icons/Icon-maskable-192.png b/todo/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/todo/web/icons/Icon-maskable-512.png b/todo/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/todo/web/index.html b/todo/web/index.html new file mode 100644 index 0000000..3e82bab --- /dev/null +++ b/todo/web/index.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + Todo + + + + + + + + diff --git a/todo/web/manifest.json b/todo/web/manifest.json new file mode 100644 index 0000000..0de9f5e --- /dev/null +++ b/todo/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Todo", + "short_name": "Todo", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A Somnio App", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} From 200381765ebb6f6f1bbd075330d8549dc337a61f Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Mon, 1 Sep 2025 11:32:04 -0300 Subject: [PATCH 2/6] fix: improve force upgrade stream handling and update iOS minimum version to 13.0 --- .../lib/src/app_config_repository.dart | 18 +- .../{{project_name.snakeCase()}}_theme.dart | 4 +- .../test/app/bloc/app_bloc_test.dart | 49 +++++ .../app/extensions/build_context_test.dart | 29 ++- .../test/helpers/pump_app.dart | 2 + todo/.gitignore | 2 + todo/android/gradlew | 0 todo/ios/Flutter/AppFrameworkInfo.plist | 2 +- todo/ios/Flutter/Debug.xcconfig | 1 + todo/ios/Flutter/Release.xcconfig | 1 + todo/ios/Podfile | 43 ++++ todo/ios/Runner.xcodeproj/project.pbxproj | 20 +- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- todo/ios/Runner/AppDelegate.swift | 2 +- todo/lib/app/router/app_router.dart | 2 +- todo/lib/app/router/auth_stream_scope.dart | 2 +- .../lib/app/router/navigation_bar_router.dart | 1 - todo/lib/app/view/app.dart | 5 +- .../view/delete_account_page.dart | 2 +- .../view/delete_account_view.dart | 6 +- .../authentication/login/view/login_page.dart | 2 +- .../authentication/login/view/login_view.dart | 6 +- .../sign_up/view/sign_up_page.dart | 2 +- .../down_for_maintenance_page.dart | 2 +- .../lib/force_upgrade/force_upgrade_page.dart | 2 +- todo/lib/home/view/home_view.dart | 3 +- .../home/view/main_screen_page_builder.dart | 2 +- todo/lib/l10n/arb/app_localizations.dart | 21 +- todo/lib/l10n/l10n.dart | 4 +- todo/lib/settings/settings.dart | 2 +- todo/lib/settings/view/settings_view.dart | 6 +- .../app_config/app_config_resource.dart | 3 +- .../lib/src/app_config_repository.dart | 18 +- todo/packages/todo_ui/build.yaml | 5 + todo/packages/todo_ui/lib/gen/assets.gen.dart | 202 ++++++++++++++++++ todo/packages/todo_ui/lib/gen/fonts.gen.dart | 15 ++ .../todo_ui/lib/src/theme/todo_theme.dart | 15 +- todo/packages/todo_ui/pubspec.yaml | 5 +- .../widgets/todo_outlined_button_test.dart | 7 +- .../todo_ui/test/theme/todo_theme_test.dart | 12 +- todo/pubspec.yaml | 6 +- todo/test/app/bloc/app_bloc_test.dart | 49 +++++ .../app/extensions/build_context_test.dart | 29 ++- todo/test/app/view/app_view_test.dart | 56 +++-- .../bloc/delete_account_bloc_test.dart | 2 +- .../sign_up/view/sign_up_view_test.dart | 2 +- .../down_for_maintenance_view_test.dart | 2 +- .../force_upgrade_view_test.dart | 2 +- todo/test/helpers/pump_app.dart | 4 +- todo/test/home/view/home_view_test.dart | 19 +- 50 files changed, 556 insertions(+), 145 deletions(-) mode change 100644 => 100755 todo/android/gradlew create mode 100644 todo/ios/Podfile create mode 100644 todo/packages/todo_ui/build.yaml create mode 100644 todo/packages/todo_ui/lib/gen/assets.gen.dart create mode 100644 todo/packages/todo_ui/lib/gen/fonts.gen.dart diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/app_config_repository/lib/src/app_config_repository.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/app_config_repository/lib/src/app_config_repository.dart index 4c29afd..426f271 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/app_config_repository/lib/src/app_config_repository.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/app_config_repository/lib/src/app_config_repository.dart @@ -33,10 +33,10 @@ class AppConfigRepository { /// Returns a [Stream] which indicates whether /// the current application requires a force upgrade. Stream isForceUpgradeRequired() { - var isUpgradeRequired = false; - var upgradeUrl = ''; - - _apiClient.appConfigResource.getUpgrade().then((upgrade) { + return _apiClient.appConfigResource.getUpgrade().asStream().map((upgrade) { + bool isUpgradeRequired; + String upgradeUrl; + if (_platform == Platform.android) { isUpgradeRequired = _buildNumber < upgrade.androidBuildNumber; upgradeUrl = upgrade.androidUpgradeUrl; @@ -44,13 +44,11 @@ class AppConfigRepository { isUpgradeRequired = _buildNumber < upgrade.iosBuildNumber; upgradeUrl = upgrade.iosUpgradeUrl; } - }); - - return Stream.value( - ForceUpgrade( + + return ForceUpgrade( isUpgradeRequired: isUpgradeRequired, upgradeUrl: isUpgradeRequired ? upgradeUrl : '', - ), - ); + ); + }); } } diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart index 659c9fb..861a101 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart @@ -39,7 +39,7 @@ class {{short_name.upperCase()}}Theme { scaffoldBackgroundColor: {{short_name.upperCase()}}Colors.black, colorScheme: _darkColorScheme, appBarTheme: _darkAppBarTheme, - disabledColor: {{short_name.upperCase()}}Colors.white.withOpacity(0.5), + disabledColor: {{short_name.upperCase()}}Colors.white.withValues(alpha: 0.5), textTheme: _darkTextTheme, unselectedWidgetColor: {{short_name.upperCase()}}Colors.lightGrey, iconTheme: _darkIconTheme, @@ -352,7 +352,7 @@ class {{short_name.upperCase()}}Theme { return BottomNavigationBarThemeData( backgroundColor: {{short_name.upperCase()}}Colors.black, selectedItemColor: {{short_name.upperCase()}}Colors.white, - unselectedItemColor: {{short_name.upperCase()}}Colors.white.withOpacity(0.74), + unselectedItemColor: {{short_name.upperCase()}}Colors.white.withValues(alpha: 0.74), ); } diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/bloc/app_bloc_test.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/bloc/app_bloc_test.dart index 9f0562d..270e31b 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/bloc/app_bloc_test.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/bloc/app_bloc_test.dart @@ -14,10 +14,28 @@ void main() { late UserRepository mockUserRepository; late AppConfigRepository appConfigRepository; late ApiClient mockApiClient; + late AppConfigResource mockAppConfigResource; setUp(() { mockUserRepository = MockUserRepository(); mockApiClient = MockApiClient(); + mockAppConfigResource = MockAppConfigResource(); + + when(() => mockApiClient.appConfigResource) + .thenReturn(mockAppConfigResource); + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.{{project_name}}', + iosUpgradeUrl: 'https://apps.apple.com/app/{{project_name}}/id123456789', + androidBuildNumber: 200, + iosBuildNumber: 200, + ), + ); + when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( + (_) async => false, + ); + appConfigRepository = AppConfigRepository( buildNumber: 201, platform: Platform.android, @@ -145,6 +163,15 @@ void main() { 'AppState:AppState(null, AppStatus.forceUpgrade)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.{{project_name}}', + iosUpgradeUrl: 'https://apps.apple.com/app/{{project_name}}/id123456789', + androidBuildNumber: 150, // Higher than 140 + iosBuildNumber: 200, + ), + ); appConfigRepository = AppConfigRepository( buildNumber: 140, platform: Platform.android, @@ -168,6 +195,15 @@ void main() { 'AppState:AppState(null, AppStatus.forceUpgrade)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.{{project_name}}', + iosUpgradeUrl: 'https://apps.apple.com/app/{{project_name}}/id123456789', + androidBuildNumber: 200, + iosBuildNumber: 150, // Higher than 100 + ), + ); appConfigRepository = AppConfigRepository( buildNumber: 100, platform: Platform.iOS, @@ -191,6 +227,19 @@ void main() { 'AppState:AppState(null, AppStatus.downForMaintenance)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.{{project_name}}', + iosUpgradeUrl: 'https://apps.apple.com/app/{{project_name}}/id123456789', + androidBuildNumber: + 50, // Lower than 99 to not trigger force upgrade + iosBuildNumber: 50, // Lower than 99 to not trigger force upgrade + ), + ); + when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( + (_) async => true, // Return true to trigger down for maintenance + ); appConfigRepository = AppConfigRepository( buildNumber: 99, platform: Platform.iOS, diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/extensions/build_context_test.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/extensions/build_context_test.dart index 88d6edd..d73e223 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/extensions/build_context_test.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/app/extensions/build_context_test.dart @@ -1,24 +1,23 @@ import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:{{project_name}}/app/app.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name}}/app/app.dart'; import 'package:user_repository/user_repository.dart'; import '../../helpers/helpers.dart'; void main() { late AppBloc mockAppBloc; - late BuildContext mockBuildContext; setUp(() { mockAppBloc = MockAppBloc(); - mockBuildContext = MockBuildContext(); }); group('BuildContextX', () { - test('user getter returns the user from AppBloc state', () { + testWidgets('user getter returns the user from AppBloc state', + (tester) async { // Mock user const testUser = User(id: 'id', email: 'email'); @@ -31,14 +30,22 @@ void main() { ), ); - // Provide the mock AppBloc using the context extension - when(() => mockBuildContext.read()).thenReturn(mockAppBloc); - - // Test the extension method - final user = mockBuildContext.user; + late User? actualUser; + + await tester.pumpWidget( + BlocProvider.value( + value: mockAppBloc, + child: Builder( + builder: (context) { + actualUser = context.user; + return Container(); + }, + ), + ), + ); // Verify that the extension method returns the correct user - expect(user, equals(testUser)); + expect(actualUser, equals(testUser)); }); }); } diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart index 35b8a19..49edb33 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart @@ -42,6 +42,8 @@ class MockNavigatorObserver extends Mock implements NavigatorObserver {} class MockApiClient extends Mock implements ApiClient {} +class MockAppConfigResource extends Mock implements AppConfigResource {} + class FakeRoute extends Fake implements Route {} class MockHomeBloc extends MockBloc implements HomeBloc {} diff --git a/todo/.gitignore b/todo/.gitignore index 187bf26..7082945 100644 --- a/todo/.gitignore +++ b/todo/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/todo/android/gradlew b/todo/android/gradlew old mode 100644 new mode 100755 diff --git a/todo/ios/Flutter/AppFrameworkInfo.plist b/todo/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..d57061d 100644 --- a/todo/ios/Flutter/AppFrameworkInfo.plist +++ b/todo/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/todo/ios/Flutter/Debug.xcconfig b/todo/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/todo/ios/Flutter/Debug.xcconfig +++ b/todo/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/todo/ios/Flutter/Release.xcconfig b/todo/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/todo/ios/Flutter/Release.xcconfig +++ b/todo/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/todo/ios/Podfile b/todo/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/todo/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/todo/ios/Runner.xcodeproj/project.pbxproj b/todo/ios/Runner.xcodeproj/project.pbxproj index f982601..4168cdd 100644 --- a/todo/ios/Runner.xcodeproj/project.pbxproj +++ b/todo/ios/Runner.xcodeproj/project.pbxproj @@ -168,7 +168,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = NO; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -350,7 +350,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -440,7 +440,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -530,7 +530,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -624,7 +624,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -714,7 +714,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -802,7 +802,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -898,7 +898,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -988,7 +988,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -1078,7 +1078,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..e3773d4 100644 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/todo/ios/Runner/AppDelegate.swift b/todo/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/todo/ios/Runner/AppDelegate.swift +++ b/todo/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/todo/lib/app/router/app_router.dart b/todo/lib/app/router/app_router.dart index 2a764de..64d004e 100644 --- a/todo/lib/app/router/app_router.dart +++ b/todo/lib/app/router/app_router.dart @@ -1,3 +1,4 @@ +import 'package:go_router/go_router.dart'; import 'package:todo/app/constants/navigation_keys.dart'; import 'package:todo/app/router/router.dart'; import 'package:todo/authentication/delete_account/delete_account.dart'; @@ -8,7 +9,6 @@ import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; import 'package:todo/force_upgrade/force_upgrade.dart'; import 'package:todo/home/home.dart'; import 'package:todo/settings/settings.dart'; -import 'package:go_router/go_router.dart'; export 'auth_stream_scope.dart'; diff --git a/todo/lib/app/router/auth_stream_scope.dart b/todo/lib/app/router/auth_stream_scope.dart index 8dbcd11..311f34c 100644 --- a/todo/lib/app/router/auth_stream_scope.dart +++ b/todo/lib/app/router/auth_stream_scope.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'package:todo/app/app.dart'; import 'package:flutter/material.dart'; +import 'package:todo/app/app.dart'; class AppStatusStreamScope extends InheritedNotifier { AppStatusStreamScope({ diff --git a/todo/lib/app/router/navigation_bar_router.dart b/todo/lib/app/router/navigation_bar_router.dart index fb3cb0c..13e4d32 100644 --- a/todo/lib/app/router/navigation_bar_router.dart +++ b/todo/lib/app/router/navigation_bar_router.dart @@ -1,4 +1,3 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:todo/home/home.dart'; import 'package:todo/settings/view/view.dart'; diff --git a/todo/lib/app/view/app.dart b/todo/lib/app/view/app.dart index 3d5cdb8..92da0a1 100644 --- a/todo/lib/app/view/app.dart +++ b/todo/lib/app/view/app.dart @@ -1,11 +1,10 @@ import 'package:app_config_repository/app_config_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:todo/app/app.dart'; -import 'package:todo/l10n/l10n.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; import 'package:todo_ui/todo_ui.dart'; -import 'package:go_router/go_router.dart'; - import 'package:user_repository/user_repository.dart'; class App extends StatelessWidget { diff --git a/todo/lib/authentication/delete_account/view/delete_account_page.dart b/todo/lib/authentication/delete_account/view/delete_account_page.dart index 3d54edc..d07acd0 100644 --- a/todo/lib/authentication/delete_account/view/delete_account_page.dart +++ b/todo/lib/authentication/delete_account/view/delete_account_page.dart @@ -1,6 +1,6 @@ -import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:user_repository/user_repository.dart'; class DeleteAccountPage extends Page { diff --git a/todo/lib/authentication/delete_account/view/delete_account_view.dart b/todo/lib/authentication/delete_account/view/delete_account_view.dart index ea570fd..fdc71c3 100644 --- a/todo/lib/authentication/delete_account/view/delete_account_view.dart +++ b/todo/lib/authentication/delete_account/view/delete_account_view.dart @@ -1,8 +1,8 @@ -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo_ui/todo_ui.dart'; -import 'package:todo/l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; +import 'package:todo/l10n/l10n.dart'; +import 'package:todo_ui/todo_ui.dart'; class DeleteAccountView extends StatelessWidget { const DeleteAccountView({super.key}); diff --git a/todo/lib/authentication/login/view/login_page.dart b/todo/lib/authentication/login/view/login_page.dart index e6d1085..094e446 100644 --- a/todo/lib/authentication/login/view/login_page.dart +++ b/todo/lib/authentication/login/view/login_page.dart @@ -1,6 +1,6 @@ -import 'package:todo/authentication/login/login.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/login/login.dart'; class LoginPage extends Page { const LoginPage({super.key}); diff --git a/todo/lib/authentication/login/view/login_view.dart b/todo/lib/authentication/login/view/login_view.dart index 3f076e9..ae45bd7 100644 --- a/todo/lib/authentication/login/view/login_view.dart +++ b/todo/lib/authentication/login/view/login_view.dart @@ -1,11 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:form_inputs/form_inputs.dart'; import 'package:go_router/go_router.dart'; import 'package:todo/authentication/forgot_password/forgot_password.dart'; import 'package:todo/authentication/login/login.dart'; import 'package:todo/authentication/sign_up/sign_up.dart'; import 'package:todo/l10n/l10n.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:form_inputs/form_inputs.dart'; import 'package:todo_ui/todo_ui.dart'; class LoginView extends StatelessWidget { diff --git a/todo/lib/authentication/sign_up/view/sign_up_page.dart b/todo/lib/authentication/sign_up/view/sign_up_page.dart index 2af9329..4de33ad 100644 --- a/todo/lib/authentication/sign_up/view/sign_up_page.dart +++ b/todo/lib/authentication/sign_up/view/sign_up_page.dart @@ -1,6 +1,6 @@ -import 'package:todo/authentication/sign_up/sign_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:todo/authentication/sign_up/sign_up.dart'; class SignUpPage extends Page { const SignUpPage(); diff --git a/todo/lib/down_for_maintenance/down_for_maintenance_page.dart b/todo/lib/down_for_maintenance/down_for_maintenance_page.dart index 39170d5..56772a8 100644 --- a/todo/lib/down_for_maintenance/down_for_maintenance_page.dart +++ b/todo/lib/down_for_maintenance/down_for_maintenance_page.dart @@ -1,5 +1,5 @@ -import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; import 'package:flutter/material.dart'; +import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; class DownForMaintenancePage extends Page { const DownForMaintenancePage({super.key}); diff --git a/todo/lib/force_upgrade/force_upgrade_page.dart b/todo/lib/force_upgrade/force_upgrade_page.dart index 18f1685..59d22dd 100644 --- a/todo/lib/force_upgrade/force_upgrade_page.dart +++ b/todo/lib/force_upgrade/force_upgrade_page.dart @@ -1,5 +1,5 @@ -import 'package:todo/force_upgrade/force_upgrade.dart'; import 'package:flutter/material.dart'; +import 'package:todo/force_upgrade/force_upgrade.dart'; class ForceUpgradePage extends Page { const ForceUpgradePage({super.key}); diff --git a/todo/lib/home/view/home_view.dart b/todo/lib/home/view/home_view.dart index 91617e2..962b815 100644 --- a/todo/lib/home/view/home_view.dart +++ b/todo/lib/home/view/home_view.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:todo/authentication/delete_account/view/delete_account_page.dart'; import 'package:todo/home/home.dart'; -import 'package:todo/app/app.dart'; import 'package:todo/l10n/l10n.dart'; class HomeView extends StatelessWidget { diff --git a/todo/lib/home/view/main_screen_page_builder.dart b/todo/lib/home/view/main_screen_page_builder.dart index 71d9c11..1e169f8 100644 --- a/todo/lib/home/view/main_screen_page_builder.dart +++ b/todo/lib/home/view/main_screen_page_builder.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:todo/navigation/navigation.dart'; import 'package:todo/home/home.dart'; +import 'package:todo/navigation/navigation.dart'; import 'package:todo/settings/settings.dart'; class MainScreenPageBuilder extends StatelessWidget { diff --git a/todo/lib/l10n/arb/app_localizations.dart b/todo/lib/l10n/arb/app_localizations.dart index 8f7f3b7..5dc8611 100644 --- a/todo/lib/l10n/arb/app_localizations.dart +++ b/todo/lib/l10n/arb/app_localizations.dart @@ -62,7 +62,7 @@ import 'app_localizations_en.dart'; /// property. abstract class AppLocalizations { AppLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -85,11 +85,11 @@ abstract class AppLocalizations { /// of delegates is preferred or required. static const List> localizationsDelegates = >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en')]; @@ -312,9 +312,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { } throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.', - ); + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); } diff --git a/todo/lib/l10n/l10n.dart b/todo/lib/l10n/l10n.dart index 17c891b..84f8c4b 100644 --- a/todo/lib/l10n/l10n.dart +++ b/todo/lib/l10n/l10n.dart @@ -1,7 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -export 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); diff --git a/todo/lib/settings/settings.dart b/todo/lib/settings/settings.dart index cdd5a84..9e1b97c 100644 --- a/todo/lib/settings/settings.dart +++ b/todo/lib/settings/settings.dart @@ -1,2 +1,2 @@ -export 'view/view.dart'; export 'bloc/settings_bloc.dart'; +export 'view/view.dart'; diff --git a/todo/lib/settings/view/settings_view.dart b/todo/lib/settings/view/settings_view.dart index 70eb18f..d0f298d 100644 --- a/todo/lib/settings/view/settings_view.dart +++ b/todo/lib/settings/view/settings_view.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:todo/app/app.dart'; import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:todo/l10n/l10n.dart'; import 'package:todo/settings/settings.dart'; import 'package:todo_ui/todo_ui.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; class SettingsView extends StatelessWidget { const SettingsView({super.key}); diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart index 5d3fc34..917ec0a 100644 --- a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart +++ b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart @@ -45,7 +45,8 @@ class AppConfigResource { if (response.statusCode == 200) { final upgradeString = jsonDecode(response.body); return Upgrade.fromJson( - Map.from(upgradeString as Map)); + Map.from(upgradeString as Map), + ); } else { throw CheckForceUpgradeFailure( Exception( diff --git a/todo/packages/app_config_repository/lib/src/app_config_repository.dart b/todo/packages/app_config_repository/lib/src/app_config_repository.dart index 4c29afd..426f271 100644 --- a/todo/packages/app_config_repository/lib/src/app_config_repository.dart +++ b/todo/packages/app_config_repository/lib/src/app_config_repository.dart @@ -33,10 +33,10 @@ class AppConfigRepository { /// Returns a [Stream] which indicates whether /// the current application requires a force upgrade. Stream isForceUpgradeRequired() { - var isUpgradeRequired = false; - var upgradeUrl = ''; - - _apiClient.appConfigResource.getUpgrade().then((upgrade) { + return _apiClient.appConfigResource.getUpgrade().asStream().map((upgrade) { + bool isUpgradeRequired; + String upgradeUrl; + if (_platform == Platform.android) { isUpgradeRequired = _buildNumber < upgrade.androidBuildNumber; upgradeUrl = upgrade.androidUpgradeUrl; @@ -44,13 +44,11 @@ class AppConfigRepository { isUpgradeRequired = _buildNumber < upgrade.iosBuildNumber; upgradeUrl = upgrade.iosUpgradeUrl; } - }); - - return Stream.value( - ForceUpgrade( + + return ForceUpgrade( isUpgradeRequired: isUpgradeRequired, upgradeUrl: isUpgradeRequired ? upgradeUrl : '', - ), - ); + ); + }); } } diff --git a/todo/packages/todo_ui/build.yaml b/todo/packages/todo_ui/build.yaml new file mode 100644 index 0000000..341dc17 --- /dev/null +++ b/todo/packages/todo_ui/build.yaml @@ -0,0 +1,5 @@ +targets: + $default: + builders: + flutter_gen_runner:flutter_gen_runner: + enabled: true diff --git a/todo/packages/todo_ui/lib/gen/assets.gen.dart b/todo/packages/todo_ui/lib/gen/assets.gen.dart new file mode 100644 index 0000000..296e6f9 --- /dev/null +++ b/todo/packages/todo_ui/lib/gen/assets.gen.dart @@ -0,0 +1,202 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart' as _svg; +import 'package:vector_graphics/vector_graphics.dart' as _vg; + +class $AssetsIconsGen { + const $AssetsIconsGen(); + + /// File path: assets/icons/back_icon.svg + SvgGenImage get backIcon => const SvgGenImage('assets/icons/back_icon.svg'); + + /// File path: assets/icons/email_outline.svg + SvgGenImage get emailOutline => + const SvgGenImage('assets/icons/email_outline.svg'); + + /// List of all assets + List get values => [backIcon, emailOutline]; +} + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/icon.png + AssetGenImage get icon => const AssetGenImage('assets/images/icon.png'); + + /// List of all assets + List get values => [icon]; +} + +class Assets { + const Assets._(); + + static const $AssetsIconsGen icons = $AssetsIconsGen(); + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage { + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); + + final String _assetName; + + final Size? size; + final Set flavors; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = true, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.medium, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({ + AssetBundle? bundle, + String? package, + }) { + return AssetImage( + _assetName, + bundle: bundle, + package: package, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} + +class SvgGenImage { + const SvgGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }) : _isVecFormat = false; + + const SvgGenImage.vec( + this._assetName, { + this.size, + this.flavors = const {}, + }) : _isVecFormat = true; + + final String _assetName; + final Size? size; + final Set flavors; + final bool _isVecFormat; + + _svg.SvgPicture svg({ + Key? key, + bool matchTextDirection = false, + AssetBundle? bundle, + String? package, + double? width, + double? height, + BoxFit fit = BoxFit.contain, + AlignmentGeometry alignment = Alignment.center, + bool allowDrawingOutsideViewBox = false, + WidgetBuilder? placeholderBuilder, + String? semanticsLabel, + bool excludeFromSemantics = false, + _svg.SvgTheme? theme, + ColorFilter? colorFilter, + Clip clipBehavior = Clip.hardEdge, + @deprecated Color? color, + @deprecated BlendMode colorBlendMode = BlendMode.srcIn, + @deprecated bool cacheColorFilter = false, + }) { + final _svg.BytesLoader loader; + if (_isVecFormat) { + loader = _vg.AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = _svg.SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } + return _svg.SvgPicture( + loader, + key: key, + matchTextDirection: matchTextDirection, + width: width, + height: height, + fit: fit, + alignment: alignment, + allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, + placeholderBuilder: placeholderBuilder, + semanticsLabel: semanticsLabel, + excludeFromSemantics: excludeFromSemantics, + colorFilter: colorFilter ?? + (color == null ? null : ColorFilter.mode(color, colorBlendMode)), + clipBehavior: clipBehavior, + cacheColorFilter: cacheColorFilter, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/todo/packages/todo_ui/lib/gen/fonts.gen.dart b/todo/packages/todo_ui/lib/gen/fonts.gen.dart new file mode 100644 index 0000000..1f17c41 --- /dev/null +++ b/todo/packages/todo_ui/lib/gen/fonts.gen.dart @@ -0,0 +1,15 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +class FontFamily { + FontFamily._(); + + /// Font family: NotoSansDisplay + static const String notoSansDisplay = 'NotoSansDisplay'; +} diff --git a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart index 84d0aa3..c5b1d6a 100644 --- a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart +++ b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart @@ -4,7 +4,6 @@ import 'package:todo_ui/todo_ui.dart'; /// A class that provides the theme for the app. class UITheme { - /// Light Theme example based on Material 2 Design. ThemeData get lightTheme { return ThemeData( @@ -39,7 +38,7 @@ class UITheme { scaffoldBackgroundColor: UIColors.black, colorScheme: _darkColorScheme, appBarTheme: _darkAppBarTheme, - disabledColor: UIColors.white.withOpacity(0.5), + disabledColor: UIColors.white.withValues(alpha: 0.5), textTheme: _darkTextTheme, unselectedWidgetColor: UIColors.lightGrey, iconTheme: _darkIconTheme, @@ -297,15 +296,13 @@ class UITheme { /// Returns the correct [SwitchThemeData] based on the current theme. SwitchThemeData get _switchTheme { return SwitchThemeData( - thumbColor: - WidgetStateProperty.resolveWith((Set states) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return UIColors.darkAqua; } return UIColors.black; }), - trackColor: - WidgetStateProperty.resolveWith((Set states) { + trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return UIColors.primaryContainer; } @@ -324,8 +321,8 @@ class UITheme { } /// Returns the correct [TabBarTheme] based on the current theme. - TabBarTheme get _tabBarTheme { - return TabBarTheme( + TabBarThemeData get _tabBarTheme { + return TabBarThemeData( labelStyle: UITextStyle.button, labelColor: UIColors.darkAqua, labelPadding: const EdgeInsets.symmetric( @@ -350,7 +347,7 @@ class UITheme { return BottomNavigationBarThemeData( backgroundColor: UIColors.black, selectedItemColor: UIColors.white, - unselectedItemColor: UIColors.white.withOpacity(0.74), + unselectedItemColor: UIColors.white.withValues(alpha: 0.74), ); } diff --git a/todo/packages/todo_ui/pubspec.yaml b/todo/packages/todo_ui/pubspec.yaml index 82de7ee..bfadcd4 100644 --- a/todo/packages/todo_ui/pubspec.yaml +++ b/todo/packages/todo_ui/pubspec.yaml @@ -35,10 +35,7 @@ flutter: weight: 600 flutter_gen: - assets: - output: - package_parameter_enabled: true + output: lib/gen/ line_length: 80 - integrations: flutter_svg: true diff --git a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart index 877a60e..a14ca3b 100644 --- a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart +++ b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart @@ -45,7 +45,10 @@ void main() { final container = tester.widget(find.byType(OutlinedButton)); - expect(container.style?.backgroundColor?.resolve({}), UIColors.lightBlue200); + expect( + container.style?.backgroundColor?.resolve({}), + UIColors.lightBlue200, + ); final textWidget = tester.widget(find.text('Primary Button')); expect(textWidget.style?.color, UIColors.black); @@ -122,4 +125,4 @@ void main() { final textWidget = tester.widget(find.text('Disabled Button')); expect(textWidget.style?.color, theme.colorScheme.onSurface); }); -} \ No newline at end of file +} diff --git a/todo/packages/todo_ui/test/theme/todo_theme_test.dart b/todo/packages/todo_ui/test/theme/todo_theme_test.dart index 6fe826d..548422d 100644 --- a/todo/packages/todo_ui/test/theme/todo_theme_test.dart +++ b/todo/packages/todo_ui/test/theme/todo_theme_test.dart @@ -93,10 +93,7 @@ void main() { test('circularTrackColor is UIColors.borderOutline', () { expect( - UITheme() - .lightTheme - .progressIndicatorTheme - .circularTrackColor, + UITheme().lightTheme.progressIndicatorTheme.circularTrackColor, equals(UIColors.borderOutline), ); }); @@ -133,7 +130,12 @@ void main() { 'padding is EdgeInsets.symmetric(horizontal: UISpacing.xlg, vertical: UISpacing.lg)', () { expect( - UITheme().lightTheme.outlinedButtonTheme.style?.padding!.resolve({}), + UITheme() + .lightTheme + .outlinedButtonTheme + .style + ?.padding! + .resolve({}), equals( const EdgeInsets.symmetric( horizontal: UISpacing.xlg, diff --git a/todo/pubspec.yaml b/todo/pubspec.yaml index 85f2274..803d4ca 100644 --- a/todo/pubspec.yaml +++ b/todo/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: path: packages/app_config_repository auth_client: path: packages/auth_client + todo_ui: + path: packages/todo_ui bloc: ^8.1.4 equatable: ^2.0.5 flutter: @@ -24,10 +26,8 @@ dependencies: form_inputs: path: packages/form_inputs go_router: ^14.2.9 - intl: ^0.19.0 + intl: ^0.20.2 package_info_plus: ^8.0.2 - temp_ui: - path: packages/temp_ui url_launcher: ^6.3.0 user_repository: path: packages/user_repository diff --git a/todo/test/app/bloc/app_bloc_test.dart b/todo/test/app/bloc/app_bloc_test.dart index 65d7fde..3a8daf3 100644 --- a/todo/test/app/bloc/app_bloc_test.dart +++ b/todo/test/app/bloc/app_bloc_test.dart @@ -14,10 +14,28 @@ void main() { late UserRepository mockUserRepository; late AppConfigRepository appConfigRepository; late ApiClient mockApiClient; + late AppConfigResource mockAppConfigResource; setUp(() { mockUserRepository = MockUserRepository(); mockApiClient = MockApiClient(); + mockAppConfigResource = MockAppConfigResource(); + + when(() => mockApiClient.appConfigResource) + .thenReturn(mockAppConfigResource); + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.todo', + iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', + androidBuildNumber: 200, + iosBuildNumber: 200, + ), + ); + when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( + (_) async => false, + ); + appConfigRepository = AppConfigRepository( buildNumber: 201, platform: Platform.android, @@ -145,6 +163,15 @@ void main() { 'AppState:AppState(null, AppStatus.forceUpgrade)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.todo', + iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', + androidBuildNumber: 150, // Higher than 140 + iosBuildNumber: 200, + ), + ); appConfigRepository = AppConfigRepository( buildNumber: 140, platform: Platform.android, @@ -168,6 +195,15 @@ void main() { 'AppState:AppState(null, AppStatus.forceUpgrade)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.todo', + iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', + androidBuildNumber: 200, + iosBuildNumber: 150, // Higher than 100 + ), + ); appConfigRepository = AppConfigRepository( buildNumber: 100, platform: Platform.iOS, @@ -191,6 +227,19 @@ void main() { 'AppState:AppState(null, AppStatus.downForMaintenance)]' ' when nothing is added', setUp: () { + when(() => mockAppConfigResource.getUpgrade()).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.todo', + iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', + androidBuildNumber: + 50, // Lower than 99 to not trigger force upgrade + iosBuildNumber: 50, // Lower than 99 to not trigger force upgrade + ), + ); + when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( + (_) async => true, // Return true to trigger down for maintenance + ); appConfigRepository = AppConfigRepository( buildNumber: 99, platform: Platform.iOS, diff --git a/todo/test/app/extensions/build_context_test.dart b/todo/test/app/extensions/build_context_test.dart index 457847e..dd904b6 100644 --- a/todo/test/app/extensions/build_context_test.dart +++ b/todo/test/app/extensions/build_context_test.dart @@ -1,24 +1,23 @@ import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/app/app.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:todo/app/app.dart'; import 'package:user_repository/user_repository.dart'; import '../../helpers/helpers.dart'; void main() { late AppBloc mockAppBloc; - late BuildContext mockBuildContext; setUp(() { mockAppBloc = MockAppBloc(); - mockBuildContext = MockBuildContext(); }); group('BuildContextX', () { - test('user getter returns the user from AppBloc state', () { + testWidgets('user getter returns the user from AppBloc state', + (tester) async { // Mock user const testUser = User(id: 'id', email: 'email'); @@ -31,14 +30,22 @@ void main() { ), ); - // Provide the mock AppBloc using the context extension - when(() => mockBuildContext.read()).thenReturn(mockAppBloc); - - // Test the extension method - final user = mockBuildContext.user; + late User? actualUser; + + await tester.pumpWidget( + BlocProvider.value( + value: mockAppBloc, + child: Builder( + builder: (context) { + actualUser = context.user; + return Container(); + }, + ), + ), + ); // Verify that the extension method returns the correct user - expect(user, equals(testUser)); + expect(actualUser, equals(testUser)); }); }); } diff --git a/todo/test/app/view/app_view_test.dart b/todo/test/app/view/app_view_test.dart index 5ac3430..c840716 100644 --- a/todo/test/app/view/app_view_test.dart +++ b/todo/test/app/view/app_view_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:api_client/api_client.dart'; import 'package:app_config_repository/app_config_repository.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; @@ -18,7 +19,6 @@ class MockUserRepository extends Mock implements UserRepository { void main() { late UserRepository mockUserRepository; - late GoRouter mockRouterConfig; late AppBloc mockAppBloc; late ApiClient mockApiClient; late AppConfigRepository mockAppConfigRepository; @@ -26,12 +26,29 @@ void main() { setUp(() { mockUserRepository = MockUserRepository(); - mockRouterConfig = MockRouterConfig(); mockAppBloc = MockAppBloc(); mockApiClient = MockApiClient(); + + // Configure AppConfigResource mock + final mockAppConfigResource = MockAppConfigResource(); + when(() => mockApiClient.appConfigResource) + .thenReturn(mockAppConfigResource); + when(mockAppConfigResource.getUpgrade).thenAnswer( + (_) async => const Upgrade( + androidUpgradeUrl: + 'https://play.google.com/store/apps/details?id=com.example.todo', + iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', + androidBuildNumber: 1, + iosBuildNumber: 1, + ), + ); + when(mockAppConfigResource.getDownForMaintenance).thenAnswer( + (_) async => false, + ); + mockAppConfigRepository = AppConfigRepository( buildNumber: 1, - platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, + platform: Platform.android, apiClient: mockApiClient, ); when(() => mockAppBloc.state).thenReturn( @@ -46,24 +63,39 @@ void main() { testWidgets('AppWidget should build correctly', (WidgetTester tester) async { await tester.pumpWidget( - App( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: mockAppConfigRepository, + MaterialApp( + home: App( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: mockAppConfigRepository, + ), ), ); expect(find.byType(MultiRepositoryProvider), findsOneWidget); expect(find.byType(BlocProvider), findsOneWidget); - expect(find.byType(AppView), findsOneWidget); }); testWidgets('AppView should build correctly', (WidgetTester tester) async { - await tester.pumpApp( - AppView( - routerConfig: mockRouterConfig, + // Create a simple router for testing + final testRouter = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const Scaffold( + body: Text('Test'), + ), + ), + ], + ); + + await tester.pumpWidget( + BlocProvider.value( + value: mockAppBloc, + child: AppView( + routerConfig: testRouter, + ), ), - appBloc: mockAppBloc, ); expect(find.byType(AppView), findsOneWidget); diff --git a/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart b/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart index f26fbf5..1942ec1 100644 --- a/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart +++ b/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart @@ -1,8 +1,8 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:form_inputs/form_inputs.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:user_repository/user_repository.dart'; import '../../../helpers/helpers.dart'; diff --git a/todo/test/authentication/sign_up/view/sign_up_view_test.dart b/todo/test/authentication/sign_up/view/sign_up_view_test.dart index 80ef53d..aae743a 100644 --- a/todo/test/authentication/sign_up/view/sign_up_view_test.dart +++ b/todo/test/authentication/sign_up/view/sign_up_view_test.dart @@ -2,8 +2,8 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:go_router/go_router.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:todo/authentication/sign_up/sign_up.dart'; import '../../../helpers/helpers.dart'; diff --git a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart index dc4d880..6e8f74f 100644 --- a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart +++ b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo/down_for_maintenance/down_for_maintenance_view.dart'; -import 'package:todo/l10n/l10n.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; void main() { group('DownForMaintenanceView', () { diff --git a/todo/test/force_upgrade/force_upgrade_view_test.dart b/todo/test/force_upgrade/force_upgrade_view_test.dart index 5465d08..0968a5c 100644 --- a/todo/test/force_upgrade/force_upgrade_view_test.dart +++ b/todo/test/force_upgrade/force_upgrade_view_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo/force_upgrade/force_upgrade.dart'; -import 'package:todo/l10n/l10n.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; void main() { group('ForceUpgradeView test', () { diff --git a/todo/test/helpers/pump_app.dart b/todo/test/helpers/pump_app.dart index 391c281..c85b2a9 100644 --- a/todo/test/helpers/pump_app.dart +++ b/todo/test/helpers/pump_app.dart @@ -11,8 +11,8 @@ import 'package:todo/authentication/delete_account/delete_account.dart'; import 'package:todo/authentication/forgot_password/forgot_password.dart'; import 'package:todo/authentication/login/login.dart'; import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:todo/l10n/l10n.dart'; import 'package:todo/home/home.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; import 'package:todo/settings/bloc/settings_bloc.dart'; import 'package:user_repository/user_repository.dart'; @@ -42,6 +42,8 @@ class MockNavigatorObserver extends Mock implements NavigatorObserver {} class MockApiClient extends Mock implements ApiClient {} +class MockAppConfigResource extends Mock implements AppConfigResource {} + class FakeRoute extends Fake implements Route {} class MockHomeBloc extends MockBloc implements HomeBloc {} diff --git a/todo/test/home/view/home_view_test.dart b/todo/test/home/view/home_view_test.dart index 8ecb946..548f52b 100644 --- a/todo/test/home/view/home_view_test.dart +++ b/todo/test/home/view/home_view_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/home/home.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:todo/home/home.dart'; import '../../helpers/helpers.dart'; @@ -26,10 +26,11 @@ void main() { ); expect( - find.byKey( - const Key('HomeAppBar'), - ), - findsOneWidget); + find.byKey( + const Key('HomeAppBar'), + ), + findsOneWidget, + ); expect(find.byType(Placeholder), findsOneWidget); }); @@ -41,9 +42,11 @@ void main() { homeBloc: mockHomeBloc, ); - await tester.tap(find.byKey( - const Key('HomeDeleteAccountButton'), - )); + await tester.tap( + find.byKey( + const Key('HomeDeleteAccountButton'), + ), + ); await tester.pumpAndSettle(); verify(() => mockNavigatorObserver.didPush(any(), any())); }); From 17464d6aec1e96915f225ddc692d9fccaed61deb Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Thu, 4 Dec 2025 16:35:31 -0300 Subject: [PATCH 3/6] chore: update local brick paths and revert iOS minimum version to 12.0 --- mason-lock.json | 2 +- .../gallery/pubspec.yaml | 4 +- .../pubspec.yaml | 5 +- .../test/src/helpers/pump_app.dart | 15 +- ...project_name.snakeCase()}}_theme_test.dart | 4 +- .../{{project_name.snakeCase()}}/pubspec.yaml | 6 +- .../test/helpers/pump_app.dart | 2 +- todo/.gitignore | 2 - todo/android/gradlew | 0 todo/ios/Flutter/AppFrameworkInfo.plist | 2 +- todo/ios/Runner.xcodeproj/project.pbxproj | 20 +- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- todo/ios/Runner/AppDelegate.swift | 2 +- todo/lib/app/view/app.dart | 2 +- todo/lib/l10n/l10n.dart | 4 +- .../app_config/app_config_resource.dart | 3 +- todo/packages/todo_ui/build.yaml | 5 - todo/packages/todo_ui/gallery/pubspec.yaml | 4 +- todo/packages/todo_ui/lib/gen/assets.gen.dart | 202 ------------------ todo/packages/todo_ui/lib/gen/fonts.gen.dart | 15 -- .../todo_ui/lib/src/theme/todo_theme.dart | 11 +- .../todo_ui/test/src/helpers/pump_app.dart | 15 +- .../widgets/todo_outlined_button_test.dart | 7 +- .../todo_ui/test/theme/todo_theme_test.dart | 16 +- todo/test/app/view/app_view_test.dart | 56 ++--- .../down_for_maintenance_view_test.dart | 2 +- .../force_upgrade_view_test.dart | 2 +- todo/test/helpers/pump_app.dart | 4 +- todo/test/home/view/home_view_test.dart | 17 +- 29 files changed, 75 insertions(+), 359 deletions(-) mode change 100755 => 100644 todo/android/gradlew delete mode 100644 todo/packages/todo_ui/build.yaml delete mode 100644 todo/packages/todo_ui/lib/gen/assets.gen.dart delete mode 100644 todo/packages/todo_ui/lib/gen/fonts.gen.dart diff --git a/mason-lock.json b/mason-lock.json index b8ea175..acd33a4 100644 --- a/mason-lock.json +++ b/mason-lock.json @@ -1 +1 @@ -{"bricks":{"dart_package":{"path":"/Users/haironchaviano/Documents/bricks/dart_package"},"feature_page_bloc":{"path":"/Users/haironchaviano/Documents/bricks/feature_page_bloc"},"feature_route_bloc":{"path":"/Users/haironchaviano/Documents/bricks/feature_route_bloc"},"flutter_package":{"path":"/Users/haironchaviano/Documents/bricks/flutter_package"},"go_router_setup":{"path":"/Users/haironchaviano/Documents/bricks/go_router_setup"},"model_json":{"path":"/Users/haironchaviano/Documents/bricks/model_json"},"rest_frog":{"path":"/Users/haironchaviano/Documents/bricks/rest_frog"},"somnio_starter":{"path":"/Users/haironchaviano/Documents/bricks/somnio_starter"},"ui_package":{"path":"/Users/haironchaviano/Documents/bricks/ui_package"}}} \ No newline at end of file +{"bricks":{"dart_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/dart_package"},"feature_page_bloc":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/feature_page_bloc"},"feature_route_bloc":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/feature_route_bloc"},"flutter_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/flutter_package"},"go_router_setup":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/go_router_setup"},"model_json":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/model_json"},"rest_frog":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/rest_frog"},"somnio_starter":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/somnio_starter"},"ui_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/ui_package"}}} \ No newline at end of file diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml index 672f411..93a21c1 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml @@ -7,11 +7,11 @@ environment: sdk: ">=2.18.0 <3.0.0" dependencies: - {{project_name.snakeCase()}}_ui: - path: ../ flutter: sdk: flutter flutter_asset_generator: ^1.0.2 + {{project_name.snakeCase()}}_ui: + path: ../ dev_dependencies: very_good_analysis: ^5.1.0 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml index 613e3a4..84e1a7c 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml @@ -35,10 +35,7 @@ flutter: weight: 600 flutter_gen: - assets: - output: - package_parameter_enabled: true + output: lib/gen/ line_length: 80 - integrations: flutter_svg: true diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/src/helpers/pump_app.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/src/helpers/pump_app.dart index 0be349b..c600695 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/src/helpers/pump_app.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/src/helpers/pump_app.dart @@ -1,26 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; extension {{short_name.upperCase()}}Tester on WidgetTester { Future pumpApp( Widget widgetUnderTest, { ThemeData? theme, - MockNavigator? navigator, }) async { await pumpWidget( MaterialApp( theme: theme, - home: navigator == null - ? Scaffold( - body: widgetUnderTest, - ) - : MockNavigatorProvider( - navigator: navigator, - child: Scaffold( - body: widgetUnderTest, - ), - ), + home: Scaffold( + body: widgetUnderTest, + ), ), ); await pump(); diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/theme/{{project_name.snakeCase()}}_theme_test.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/theme/{{project_name.snakeCase()}}_theme_test.dart index 991e547..fc09522 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/theme/{{project_name.snakeCase()}}_theme_test.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/test/theme/{{project_name.snakeCase()}}_theme_test.dart @@ -130,7 +130,7 @@ void main() { }); test( - 'padding is EdgeInsets.symmetric(horizontal: {{short_name.upperCase()}}Spacing.xlg, vertical: {{short_name.upperCase()}}Spacing.lg)', + 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', () { expect( {{short_name.upperCase()}}Theme().lightTheme.outlinedButtonTheme.style?.padding!.resolve({}), @@ -265,7 +265,7 @@ void main() { }); test( - 'padding is EdgeInsets.symmetric(horizontal: {{short_name.upperCase()}}Spacing.xlg, vertical: {{short_name.upperCase()}}Spacing.lg)', + 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', () { expect( {{short_name.upperCase()}}Theme().darkTheme.outlinedButtonTheme.style?.padding?.resolve({}), diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/pubspec.yaml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/pubspec.yaml index 64654f8..4dadc56 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/pubspec.yaml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: path: packages/app_config_repository auth_client: path: packages/auth_client + {{project_name.snakeCase()}}_ui: + path: packages/{{project_name.snakeCase()}}_ui bloc: ^8.1.4 equatable: ^2.0.5 flutter: @@ -24,10 +26,8 @@ dependencies: form_inputs: path: packages/form_inputs go_router: ^14.2.9 - intl: ^0.19.0 + intl: ^0.20.2 package_info_plus: ^8.0.2 - temp_ui: - path: packages/temp_ui url_launcher: ^6.3.0 user_repository: path: packages/user_repository diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart index 49edb33..ada99e0 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/test/helpers/pump_app.dart @@ -122,7 +122,7 @@ extension AppTester on WidgetTester { ), ], child: MaterialApp.router( - title: 'Somnio Starter', + title: '{{project_name.titleCase()}}', localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/todo/.gitignore b/todo/.gitignore index 7082945..187bf26 100644 --- a/todo/.gitignore +++ b/todo/.gitignore @@ -5,11 +5,9 @@ *.swp .DS_Store .atom/ -.build/ .buildlog/ .history .svn/ -.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/todo/android/gradlew b/todo/android/gradlew old mode 100755 new mode 100644 diff --git a/todo/ios/Flutter/AppFrameworkInfo.plist b/todo/ios/Flutter/AppFrameworkInfo.plist index d57061d..4f8d4d2 100644 --- a/todo/ios/Flutter/AppFrameworkInfo.plist +++ b/todo/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 13.0 + 11.0 diff --git a/todo/ios/Runner.xcodeproj/project.pbxproj b/todo/ios/Runner.xcodeproj/project.pbxproj index 4168cdd..f982601 100644 --- a/todo/ios/Runner.xcodeproj/project.pbxproj +++ b/todo/ios/Runner.xcodeproj/project.pbxproj @@ -168,7 +168,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = NO; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -350,7 +350,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -440,7 +440,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -530,7 +530,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -624,7 +624,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -714,7 +714,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -802,7 +802,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -898,7 +898,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -988,7 +988,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -1078,7 +1078,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..87131a0 100644 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/todo/ios/Runner/AppDelegate.swift b/todo/ios/Runner/AppDelegate.swift index b636303..70693e4 100644 --- a/todo/ios/Runner/AppDelegate.swift +++ b/todo/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@main +@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/todo/lib/app/view/app.dart b/todo/lib/app/view/app.dart index 92da0a1..3bfac7e 100644 --- a/todo/lib/app/view/app.dart +++ b/todo/lib/app/view/app.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:todo/app/app.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; +import 'package:todo/l10n/l10n.dart'; import 'package:todo_ui/todo_ui.dart'; import 'package:user_repository/user_repository.dart'; diff --git a/todo/lib/l10n/l10n.dart b/todo/lib/l10n/l10n.dart index 84f8c4b..17c891b 100644 --- a/todo/lib/l10n/l10n.dart +++ b/todo/lib/l10n/l10n.dart @@ -1,5 +1,7 @@ import 'package:flutter/widgets.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +export 'package:flutter_gen/gen_l10n/app_localizations.dart'; extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart index 917ec0a..3c28b9f 100644 --- a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart +++ b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart @@ -45,8 +45,7 @@ class AppConfigResource { if (response.statusCode == 200) { final upgradeString = jsonDecode(response.body); return Upgrade.fromJson( - Map.from(upgradeString as Map), - ); + Map.from(upgradeString as Map),); } else { throw CheckForceUpgradeFailure( Exception( diff --git a/todo/packages/todo_ui/build.yaml b/todo/packages/todo_ui/build.yaml deleted file mode 100644 index 341dc17..0000000 --- a/todo/packages/todo_ui/build.yaml +++ /dev/null @@ -1,5 +0,0 @@ -targets: - $default: - builders: - flutter_gen_runner:flutter_gen_runner: - enabled: true diff --git a/todo/packages/todo_ui/gallery/pubspec.yaml b/todo/packages/todo_ui/gallery/pubspec.yaml index 23cc13f..0783679 100644 --- a/todo/packages/todo_ui/gallery/pubspec.yaml +++ b/todo/packages/todo_ui/gallery/pubspec.yaml @@ -7,11 +7,11 @@ environment: sdk: ">=2.18.0 <3.0.0" dependencies: - todo_ui: - path: ../ flutter: sdk: flutter flutter_asset_generator: ^1.0.2 + todo_ui: + path: ../ dev_dependencies: very_good_analysis: ^5.1.0 diff --git a/todo/packages/todo_ui/lib/gen/assets.gen.dart b/todo/packages/todo_ui/lib/gen/assets.gen.dart deleted file mode 100644 index 296e6f9..0000000 --- a/todo/packages/todo_ui/lib/gen/assets.gen.dart +++ /dev/null @@ -1,202 +0,0 @@ -/// GENERATED CODE - DO NOT MODIFY BY HAND -/// ***************************************************** -/// FlutterGen -/// ***************************************************** - -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use - -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_svg/flutter_svg.dart' as _svg; -import 'package:vector_graphics/vector_graphics.dart' as _vg; - -class $AssetsIconsGen { - const $AssetsIconsGen(); - - /// File path: assets/icons/back_icon.svg - SvgGenImage get backIcon => const SvgGenImage('assets/icons/back_icon.svg'); - - /// File path: assets/icons/email_outline.svg - SvgGenImage get emailOutline => - const SvgGenImage('assets/icons/email_outline.svg'); - - /// List of all assets - List get values => [backIcon, emailOutline]; -} - -class $AssetsImagesGen { - const $AssetsImagesGen(); - - /// File path: assets/images/icon.png - AssetGenImage get icon => const AssetGenImage('assets/images/icon.png'); - - /// List of all assets - List get values => [icon]; -} - -class Assets { - const Assets._(); - - static const $AssetsIconsGen icons = $AssetsIconsGen(); - static const $AssetsImagesGen images = $AssetsImagesGen(); -} - -class AssetGenImage { - const AssetGenImage( - this._assetName, { - this.size, - this.flavors = const {}, - }); - - final String _assetName; - - final Size? size; - final Set flavors; - - Image image({ - Key? key, - AssetBundle? bundle, - ImageFrameBuilder? frameBuilder, - ImageErrorWidgetBuilder? errorBuilder, - String? semanticLabel, - bool excludeFromSemantics = false, - double? scale, - double? width, - double? height, - Color? color, - Animation? opacity, - BlendMode? colorBlendMode, - BoxFit? fit, - AlignmentGeometry alignment = Alignment.center, - ImageRepeat repeat = ImageRepeat.noRepeat, - Rect? centerSlice, - bool matchTextDirection = false, - bool gaplessPlayback = true, - bool isAntiAlias = false, - String? package, - FilterQuality filterQuality = FilterQuality.medium, - int? cacheWidth, - int? cacheHeight, - }) { - return Image.asset( - _assetName, - key: key, - bundle: bundle, - frameBuilder: frameBuilder, - errorBuilder: errorBuilder, - semanticLabel: semanticLabel, - excludeFromSemantics: excludeFromSemantics, - scale: scale, - width: width, - height: height, - color: color, - opacity: opacity, - colorBlendMode: colorBlendMode, - fit: fit, - alignment: alignment, - repeat: repeat, - centerSlice: centerSlice, - matchTextDirection: matchTextDirection, - gaplessPlayback: gaplessPlayback, - isAntiAlias: isAntiAlias, - package: package, - filterQuality: filterQuality, - cacheWidth: cacheWidth, - cacheHeight: cacheHeight, - ); - } - - ImageProvider provider({ - AssetBundle? bundle, - String? package, - }) { - return AssetImage( - _assetName, - bundle: bundle, - package: package, - ); - } - - String get path => _assetName; - - String get keyName => _assetName; -} - -class SvgGenImage { - const SvgGenImage( - this._assetName, { - this.size, - this.flavors = const {}, - }) : _isVecFormat = false; - - const SvgGenImage.vec( - this._assetName, { - this.size, - this.flavors = const {}, - }) : _isVecFormat = true; - - final String _assetName; - final Size? size; - final Set flavors; - final bool _isVecFormat; - - _svg.SvgPicture svg({ - Key? key, - bool matchTextDirection = false, - AssetBundle? bundle, - String? package, - double? width, - double? height, - BoxFit fit = BoxFit.contain, - AlignmentGeometry alignment = Alignment.center, - bool allowDrawingOutsideViewBox = false, - WidgetBuilder? placeholderBuilder, - String? semanticsLabel, - bool excludeFromSemantics = false, - _svg.SvgTheme? theme, - ColorFilter? colorFilter, - Clip clipBehavior = Clip.hardEdge, - @deprecated Color? color, - @deprecated BlendMode colorBlendMode = BlendMode.srcIn, - @deprecated bool cacheColorFilter = false, - }) { - final _svg.BytesLoader loader; - if (_isVecFormat) { - loader = _vg.AssetBytesLoader( - _assetName, - assetBundle: bundle, - packageName: package, - ); - } else { - loader = _svg.SvgAssetLoader( - _assetName, - assetBundle: bundle, - packageName: package, - theme: theme, - ); - } - return _svg.SvgPicture( - loader, - key: key, - matchTextDirection: matchTextDirection, - width: width, - height: height, - fit: fit, - alignment: alignment, - allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, - placeholderBuilder: placeholderBuilder, - semanticsLabel: semanticsLabel, - excludeFromSemantics: excludeFromSemantics, - colorFilter: colorFilter ?? - (color == null ? null : ColorFilter.mode(color, colorBlendMode)), - clipBehavior: clipBehavior, - cacheColorFilter: cacheColorFilter, - ); - } - - String get path => _assetName; - - String get keyName => _assetName; -} diff --git a/todo/packages/todo_ui/lib/gen/fonts.gen.dart b/todo/packages/todo_ui/lib/gen/fonts.gen.dart deleted file mode 100644 index 1f17c41..0000000 --- a/todo/packages/todo_ui/lib/gen/fonts.gen.dart +++ /dev/null @@ -1,15 +0,0 @@ -/// GENERATED CODE - DO NOT MODIFY BY HAND -/// ***************************************************** -/// FlutterGen -/// ***************************************************** - -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use - -class FontFamily { - FontFamily._(); - - /// Font family: NotoSansDisplay - static const String notoSansDisplay = 'NotoSansDisplay'; -} diff --git a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart index c5b1d6a..6aa0cc1 100644 --- a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart +++ b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart @@ -4,6 +4,7 @@ import 'package:todo_ui/todo_ui.dart'; /// A class that provides the theme for the app. class UITheme { + /// Light Theme example based on Material 2 Design. ThemeData get lightTheme { return ThemeData( @@ -296,13 +297,15 @@ class UITheme { /// Returns the correct [SwitchThemeData] based on the current theme. SwitchThemeData get _switchTheme { return SwitchThemeData( - thumbColor: WidgetStateProperty.resolveWith((Set states) { + thumbColor: + WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return UIColors.darkAqua; } return UIColors.black; }), - trackColor: WidgetStateProperty.resolveWith((Set states) { + trackColor: + WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return UIColors.primaryContainer; } @@ -321,8 +324,8 @@ class UITheme { } /// Returns the correct [TabBarTheme] based on the current theme. - TabBarThemeData get _tabBarTheme { - return TabBarThemeData( + TabBarTheme get _tabBarTheme { + return TabBarTheme( labelStyle: UITextStyle.button, labelColor: UIColors.darkAqua, labelPadding: const EdgeInsets.symmetric( diff --git a/todo/packages/todo_ui/test/src/helpers/pump_app.dart b/todo/packages/todo_ui/test/src/helpers/pump_app.dart index 7b4c6e9..7b95b1d 100644 --- a/todo/packages/todo_ui/test/src/helpers/pump_app.dart +++ b/todo/packages/todo_ui/test/src/helpers/pump_app.dart @@ -1,26 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; extension UITester on WidgetTester { Future pumpApp( Widget widgetUnderTest, { ThemeData? theme, - MockNavigator? navigator, }) async { await pumpWidget( MaterialApp( theme: theme, - home: navigator == null - ? Scaffold( - body: widgetUnderTest, - ) - : MockNavigatorProvider( - navigator: navigator, - child: Scaffold( - body: widgetUnderTest, - ), - ), + home: Scaffold( + body: widgetUnderTest, + ), ), ); await pump(); diff --git a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart index a14ca3b..877a60e 100644 --- a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart +++ b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart @@ -45,10 +45,7 @@ void main() { final container = tester.widget(find.byType(OutlinedButton)); - expect( - container.style?.backgroundColor?.resolve({}), - UIColors.lightBlue200, - ); + expect(container.style?.backgroundColor?.resolve({}), UIColors.lightBlue200); final textWidget = tester.widget(find.text('Primary Button')); expect(textWidget.style?.color, UIColors.black); @@ -125,4 +122,4 @@ void main() { final textWidget = tester.widget(find.text('Disabled Button')); expect(textWidget.style?.color, theme.colorScheme.onSurface); }); -} +} \ No newline at end of file diff --git a/todo/packages/todo_ui/test/theme/todo_theme_test.dart b/todo/packages/todo_ui/test/theme/todo_theme_test.dart index 548422d..0a03d8d 100644 --- a/todo/packages/todo_ui/test/theme/todo_theme_test.dart +++ b/todo/packages/todo_ui/test/theme/todo_theme_test.dart @@ -93,7 +93,10 @@ void main() { test('circularTrackColor is UIColors.borderOutline', () { expect( - UITheme().lightTheme.progressIndicatorTheme.circularTrackColor, + UITheme() + .lightTheme + .progressIndicatorTheme + .circularTrackColor, equals(UIColors.borderOutline), ); }); @@ -127,15 +130,10 @@ void main() { }); test( - 'padding is EdgeInsets.symmetric(horizontal: UISpacing.xlg, vertical: UISpacing.lg)', + 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', () { expect( - UITheme() - .lightTheme - .outlinedButtonTheme - .style - ?.padding! - .resolve({}), + UITheme().lightTheme.outlinedButtonTheme.style?.padding!.resolve({}), equals( const EdgeInsets.symmetric( horizontal: UISpacing.xlg, @@ -267,7 +265,7 @@ void main() { }); test( - 'padding is EdgeInsets.symmetric(horizontal: UISpacing.xlg, vertical: UISpacing.lg)', + 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', () { expect( UITheme().darkTheme.outlinedButtonTheme.style?.padding?.resolve({}), diff --git a/todo/test/app/view/app_view_test.dart b/todo/test/app/view/app_view_test.dart index c840716..5ac3430 100644 --- a/todo/test/app/view/app_view_test.dart +++ b/todo/test/app/view/app_view_test.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:api_client/api_client.dart'; import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; @@ -19,6 +18,7 @@ class MockUserRepository extends Mock implements UserRepository { void main() { late UserRepository mockUserRepository; + late GoRouter mockRouterConfig; late AppBloc mockAppBloc; late ApiClient mockApiClient; late AppConfigRepository mockAppConfigRepository; @@ -26,29 +26,12 @@ void main() { setUp(() { mockUserRepository = MockUserRepository(); + mockRouterConfig = MockRouterConfig(); mockAppBloc = MockAppBloc(); mockApiClient = MockApiClient(); - - // Configure AppConfigResource mock - final mockAppConfigResource = MockAppConfigResource(); - when(() => mockApiClient.appConfigResource) - .thenReturn(mockAppConfigResource); - when(mockAppConfigResource.getUpgrade).thenAnswer( - (_) async => const Upgrade( - androidUpgradeUrl: - 'https://play.google.com/store/apps/details?id=com.example.todo', - iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', - androidBuildNumber: 1, - iosBuildNumber: 1, - ), - ); - when(mockAppConfigResource.getDownForMaintenance).thenAnswer( - (_) async => false, - ); - mockAppConfigRepository = AppConfigRepository( buildNumber: 1, - platform: Platform.android, + platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, apiClient: mockApiClient, ); when(() => mockAppBloc.state).thenReturn( @@ -63,39 +46,24 @@ void main() { testWidgets('AppWidget should build correctly', (WidgetTester tester) async { await tester.pumpWidget( - MaterialApp( - home: App( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: mockAppConfigRepository, - ), + App( + userRepository: mockUserRepository, + user: testUser, + appConfigRepository: mockAppConfigRepository, ), ); expect(find.byType(MultiRepositoryProvider), findsOneWidget); expect(find.byType(BlocProvider), findsOneWidget); + expect(find.byType(AppView), findsOneWidget); }); testWidgets('AppView should build correctly', (WidgetTester tester) async { - // Create a simple router for testing - final testRouter = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const Scaffold( - body: Text('Test'), - ), - ), - ], - ); - - await tester.pumpWidget( - BlocProvider.value( - value: mockAppBloc, - child: AppView( - routerConfig: testRouter, - ), + await tester.pumpApp( + AppView( + routerConfig: mockRouterConfig, ), + appBloc: mockAppBloc, ); expect(find.byType(AppView), findsOneWidget); diff --git a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart index 6e8f74f..dc4d880 100644 --- a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart +++ b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo/down_for_maintenance/down_for_maintenance_view.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; +import 'package:todo/l10n/l10n.dart'; void main() { group('DownForMaintenanceView', () { diff --git a/todo/test/force_upgrade/force_upgrade_view_test.dart b/todo/test/force_upgrade/force_upgrade_view_test.dart index 0968a5c..5465d08 100644 --- a/todo/test/force_upgrade/force_upgrade_view_test.dart +++ b/todo/test/force_upgrade/force_upgrade_view_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo/force_upgrade/force_upgrade.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; +import 'package:todo/l10n/l10n.dart'; void main() { group('ForceUpgradeView test', () { diff --git a/todo/test/helpers/pump_app.dart b/todo/test/helpers/pump_app.dart index c85b2a9..b1a435b 100644 --- a/todo/test/helpers/pump_app.dart +++ b/todo/test/helpers/pump_app.dart @@ -12,7 +12,7 @@ import 'package:todo/authentication/forgot_password/forgot_password.dart'; import 'package:todo/authentication/login/login.dart'; import 'package:todo/authentication/sign_up/sign_up.dart'; import 'package:todo/home/home.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; +import 'package:todo/l10n/l10n.dart'; import 'package:todo/settings/bloc/settings_bloc.dart'; import 'package:user_repository/user_repository.dart'; @@ -122,7 +122,7 @@ extension AppTester on WidgetTester { ), ], child: MaterialApp.router( - title: 'Somnio Starter', + title: 'Todo', localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/todo/test/home/view/home_view_test.dart b/todo/test/home/view/home_view_test.dart index 548f52b..7201508 100644 --- a/todo/test/home/view/home_view_test.dart +++ b/todo/test/home/view/home_view_test.dart @@ -26,11 +26,10 @@ void main() { ); expect( - find.byKey( - const Key('HomeAppBar'), - ), - findsOneWidget, - ); + find.byKey( + const Key('HomeAppBar'), + ), + findsOneWidget,); expect(find.byType(Placeholder), findsOneWidget); }); @@ -42,11 +41,9 @@ void main() { homeBloc: mockHomeBloc, ); - await tester.tap( - find.byKey( - const Key('HomeDeleteAccountButton'), - ), - ); + await tester.tap(find.byKey( + const Key('HomeDeleteAccountButton'), + ),); await tester.pumpAndSettle(); verify(() => mockNavigatorObserver.didPush(any(), any())); }); From 94690ad309e7416400257b15bc8a5f495d807c22 Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Wed, 10 Dec 2025 17:46:23 -0300 Subject: [PATCH 4/6] feat: update localization import path, rename `TabBarTheme` to `TabBarThemeData`, and refresh mason brick paths --- mason-lock.json | 2 +- .../{{project_name.snakeCase()}}/lib/l10n/l10n.dart | 5 ++--- .../lib/src/theme/{{project_name.snakeCase()}}_theme.dart | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mason-lock.json b/mason-lock.json index acd33a4..a8571e7 100644 --- a/mason-lock.json +++ b/mason-lock.json @@ -1 +1 @@ -{"bricks":{"dart_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/dart_package"},"feature_page_bloc":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/feature_page_bloc"},"feature_route_bloc":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/feature_route_bloc"},"flutter_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/flutter_package"},"go_router_setup":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/go_router_setup"},"model_json":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/model_json"},"rest_frog":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/rest_frog"},"somnio_starter":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/somnio_starter"},"ui_package":{"path":"/Users/marcostort/Documents/trabajo/bricks-1/ui_package"}}} \ No newline at end of file +{"bricks":{"dart_package":{"path":"/Users/marcostort/Documents/bricks/dart_package"},"feature_page_bloc":{"path":"/Users/marcostort/Documents/bricks/feature_page_bloc"},"feature_route_bloc":{"path":"/Users/marcostort/Documents/bricks/feature_route_bloc"},"flutter_package":{"path":"/Users/marcostort/Documents/bricks/flutter_package"},"go_router_setup":{"path":"/Users/marcostort/Documents/bricks/go_router_setup"},"model_json":{"path":"/Users/marcostort/Documents/bricks/model_json"},"rest_frog":{"path":"/Users/marcostort/Documents/bricks/rest_frog"},"somnio_starter":{"path":"/Users/marcostort/Documents/bricks/somnio_starter"},"ui_package":{"path":"/Users/marcostort/Documents/bricks/ui_package"}}} \ No newline at end of file diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/lib/l10n/l10n.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/lib/l10n/l10n.dart index 17c891b..834da1c 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/lib/l10n/l10n.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/lib/l10n/l10n.dart @@ -1,8 +1,7 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -export 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:{{project_name.snakeCase()}}/l10n/arb/app_localizations.dart'; +export 'package:{{project_name.snakeCase()}}/l10n/arb/app_localizations.dart'; extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); } diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart index 861a101..3bb4712 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/lib/src/theme/{{project_name.snakeCase()}}_theme.dart @@ -326,8 +326,8 @@ class {{short_name.upperCase()}}Theme { } /// Returns the correct [TabBarTheme] based on the current theme. - TabBarTheme get _tabBarTheme { - return TabBarTheme( + TabBarThemeData get _tabBarTheme { + return TabBarThemeData( labelStyle: {{short_name.upperCase()}}TextStyle.button, labelColor: {{short_name.upperCase()}}Colors.darkAqua, labelPadding: const EdgeInsets.symmetric( From 30faa2471ced16af482f8acfdefb8043eaead5c1 Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Wed, 10 Dec 2025 17:56:55 -0300 Subject: [PATCH 5/6] feat: Migrate Android build files to Kotlin DSL and remove environment-specific app icons and schemes. --- bricks.iml | 17 + .../android/.gitignore | 5 +- .../android/app/build.gradle | 115 --- .../android/app/build.gradle.kts | 44 ++ .../android/app/src/debug/AndroidManifest.xml | 3 +- .../res/drawable-v21/launch_background.xml | 12 - .../res/drawable/launch_background.xml | 12 - .../res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../android/app/src/main/AndroidManifest.xml | 23 +- .../kotlin/com/example/bricks/MainActivity.kt | 5 + .../{{org_name.pathCase()}}/MainActivity.kt | 6 - .../app/src/profile/AndroidManifest.xml | 3 +- .../res/drawable-v21/launch_background.xml | 12 - .../res/drawable/launch_background.xml | 12 - .../staging/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../staging/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../staging/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../staging/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../android/build.gradle | 18 - .../android/build.gradle.kts | 24 + .../android/gradle.properties | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../android/gradlew | 0 .../android/settings.gradle | 26 - .../android/settings.gradle.kts | 26 + .../ios/Flutter/AppFrameworkInfo.plist | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 685 ++---------------- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- .../xcschemes/development.xcscheme | 78 -- .../xcschemes/production.xcscheme | 78 -- .../xcshareddata/xcschemes/staging.xcscheme | 78 -- .../ios/Runner/AppDelegate.swift | 4 +- .../AppIcon-dev.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../AppIcon-stg.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../Runner/Base.lproj/LaunchScreen.storyboard | 14 +- .../ios/Runner/Info.plist | 11 +- somnio_starter/hooks/post_gen.dart | 7 +- todo/android/.gitignore | 5 +- todo/android/app/build.gradle | 115 --- todo/android/app/build.gradle.kts | 44 ++ .../android/app/src/debug/AndroidManifest.xml | 3 +- .../res/drawable-v21/launch_background.xml | 12 - .../res/drawable/launch_background.xml | 12 - .../res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes todo/android/app/src/main/AndroidManifest.xml | 23 +- .../main/kotlin/com/example/MainActivity.kt | 6 - .../kotlin/com/example/bricks/MainActivity.kt | 5 + .../app/src/profile/AndroidManifest.xml | 3 +- .../res/drawable-v21/launch_background.xml | 12 - .../res/drawable/launch_background.xml | 12 - .../staging/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../staging/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../staging/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../staging/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes todo/android/build.gradle | 18 - todo/android/build.gradle.kts | 24 + todo/android/gradle.properties | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- todo/android/settings.gradle | 26 - todo/android/settings.gradle.kts | 26 + todo/ios/Flutter/AppFrameworkInfo.plist | 4 +- todo/ios/Runner.xcodeproj/project.pbxproj | 685 ++---------------- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- .../xcschemes/development.xcscheme | 78 -- .../xcschemes/production.xcscheme | 78 -- .../xcshareddata/xcschemes/staging.xcscheme | 78 -- todo/ios/Runner/AppDelegate.swift | 4 +- .../AppIcon-dev.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../AppIcon-stg.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../Runner/Base.lproj/LaunchScreen.storyboard | 14 +- todo/ios/Runner/Info.plist | 11 +- todo/lib/l10n/l10n.dart | 5 +- .../todo_ui/lib/src/theme/todo_theme.dart | 4 +- 143 files changed, 427 insertions(+), 2712 deletions(-) create mode 100644 bricks.iml delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle create mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle.kts delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/drawable-v21/launch_background.xml delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/drawable/launch_background.xml delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-hdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-mdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/{{org_name.pathCase()}}/MainActivity.kt delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/drawable-v21/launch_background.xml delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/drawable/launch_background.xml delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle create mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle.kts mode change 100644 => 100755 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradlew delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle create mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle.kts delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@1x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@2x.png delete mode 100644 somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 todo/android/app/build.gradle create mode 100644 todo/android/app/build.gradle.kts delete mode 100644 todo/android/app/src/development/res/drawable-v21/launch_background.xml delete mode 100644 todo/android/app/src/development/res/drawable/launch_background.xml delete mode 100644 todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png delete mode 100644 todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png delete mode 100644 todo/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/kotlin/com/example/MainActivity.kt create mode 100644 todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt delete mode 100644 todo/android/app/src/staging/res/drawable-v21/launch_background.xml delete mode 100644 todo/android/app/src/staging/res/drawable/launch_background.xml delete mode 100644 todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png delete mode 100644 todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png delete mode 100644 todo/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 todo/android/build.gradle create mode 100644 todo/android/build.gradle.kts delete mode 100644 todo/android/settings.gradle create mode 100644 todo/android/settings.gradle.kts delete mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme delete mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme delete mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-76x76@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-76x76@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/bricks.iml b/bricks.iml new file mode 100644 index 0000000..f66303d --- /dev/null +++ b/bricks.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/.gitignore b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/.gitignore index d6aa291..be3943c 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/.gitignore +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/.gitignore @@ -1,11 +1,14 @@ gradle-wrapper.jar /.gradle /captures/ +/gradlew +/gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle deleted file mode 100644 index 9d65d87..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle +++ /dev/null @@ -1,115 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - -android { - namespace "{{org_name.dotCase()}}.{{project_name.snakeCase()}}" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "{{org_name.dotCase()}}.{{project_name.snakeCase()}}" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - signingConfigs { - if (System.getenv("ANDROID_KEYSTORE_PATH")) { - release { - storeFile file(System.getenv("ANDROID_KEYSTORE_PATH")) - keyAlias System.getenv("ANDROID_KEYSTORE_ALIAS") - keyPassword System.getenv("ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD") - storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") - } - } else { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } - } - - flavorDimensions "default" - productFlavors { - production { - dimension "default" - applicationIdSuffix "" - manifestPlaceholders = [appName: "{{project_name.titleCase()}}"] - } - staging { - dimension "default" - applicationIdSuffix ".stg" - manifestPlaceholders = [appName: "[STG] {{project_name.titleCase()}}"] - } - development { - dimension "default" - applicationIdSuffix ".dev" - manifestPlaceholders = [appName: "[DEV] {{project_name.titleCase()}}"] - } - } - - buildTypes { - release { - signingConfig signingConfigs.release - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt') - } - debug { - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10" -} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle.kts b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle.kts new file mode 100644 index 0000000..0202476 --- /dev/null +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.bricks" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.bricks" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/debug/AndroidManifest.xml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/debug/AndroidManifest.xml index c026bfb..399f698 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/debug/AndroidManifest.xml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/drawable/launch_background.xml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-hdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-mdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/AndroidManifest.xml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/AndroidManifest.xml index e6d7f4b..29c379f 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/AndroidManifest.xml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + /> @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt new file mode 100644 index 0000000..ac09d0e --- /dev/null +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.bricks + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/{{org_name.pathCase()}}/MainActivity.kt b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/{{org_name.pathCase()}}/MainActivity.kt deleted file mode 100644 index 28ecd3b..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/main/kotlin/{{org_name.pathCase()}}/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package {{org_name.dotCase()}}.{{project_name.snakeCase()}} - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/profile/AndroidManifest.xml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/profile/AndroidManifest.xml index c026bfb..399f698 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/profile/AndroidManifest.xml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/drawable/launch_background.xml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle deleted file mode 100644 index bc157bd..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle.kts b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle.properties b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle.properties index 94adc3a..fbee1d8 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle.properties +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle.properties @@ -1,3 +1,2 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle/wrapper/gradle-wrapper.properties b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle/wrapper/gradle-wrapper.properties index 6b66533..e4ef43f 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle/wrapper/gradle-wrapper.properties +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradlew b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/gradlew old mode 100644 new mode 100755 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle deleted file mode 100644 index 1d6d19b..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false -} - -include ":app" diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle.kts b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Flutter/AppFrameworkInfo.plist b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..1dc6cf7 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Flutter/AppFrameworkInfo.plist +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/project.pbxproj b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/project.pbxproj index 5758e1d..42ac687 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/project.pbxproj +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -68,7 +68,7 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C807B294A618700263BE5 /* RunnerTests.swift */, @@ -124,7 +124,7 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { + 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( @@ -167,11 +167,11 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = NO; - LastUpgradeCheck = 1430; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { - 331C8080294A63A400263BE5 = { + 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -201,7 +201,7 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { + 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -256,7 +256,7 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { + 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -303,106 +303,11 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 3C9C55192B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Debug-production"; - }; - 3C9C551A2B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "{{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-production"; - }; - 3C9C551B2B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Debug-production"; - }; - 3C9C551C2B07ACA4000E5FCD /* Release-production */ = { + 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -432,6 +337,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -440,41 +346,38 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; - name = "Release-production"; + name = Profile; }; - 3C9C551D2B07ACA4000E5FCD /* Release-production */ = { + 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "{{project_name.titleCase()}}"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Release-production"; + name = Profile; }; - 3C9C551E2B07ACA4000E5FCD /* Release-production */ = { + 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -482,271 +385,16 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Release-production"; - }; - 3C9C55222B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-production"; - }; - 3C9C55232B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "{{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Profile-production"; - }; - 3C9C55242B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Profile-production"; - }; - 3C9C55252B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Debug-development"; - }; - 3C9C55262B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] {{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-development"; - }; - 3C9C55272B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Debug-development"; + name = Debug; }; - 3C9C552B2B07AD09000E5FCD /* Profile-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-development"; - }; - 3C9C552C2B07AD09000E5FCD /* Profile-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] {{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Profile-development"; - }; - 3C9C552D2B07AD09000E5FCD /* Profile-development */ = { + 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -754,89 +402,14 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Profile-development"; - }; - 3C9C552E2B07AD1B000E5FCD /* Release-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = "Release-development"; + name = Release; }; - 3C9C552F2B07AD1B000E5FCD /* Release-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] {{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Release-development"; - }; - 3C9C55302B07AD1B000E5FCD /* Release-development */ = { + 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -844,17 +417,18 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Release-development"; + name = Profile; }; - 3C9C55312B07AD26000E5FCD /* Debug-staging */ = { + 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -884,6 +458,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -898,59 +473,19 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; - name = "Debug-staging"; - }; - 3C9C55322B07AD26000E5FCD /* Debug-staging */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] {{project_name.titleCase()}}"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.stg; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-staging"; + name = Debug; }; - 3C9C55332B07AD26000E5FCD /* Debug-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Debug-staging"; - }; - 3C9C55342B07AD41000E5FCD /* Release-staging */ = { + 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -980,6 +515,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -988,7 +524,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -997,184 +533,85 @@ TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; - name = "Release-staging"; + name = Release; }; - 3C9C55352B07AD41000E5FCD /* Release-staging */ = { + 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] {{project_name.titleCase()}}"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.stg; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Release-staging"; - }; - 3C9C55362B07AD41000E5FCD /* Release-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Release-staging"; + name = Debug; }; - 3C9C55372B07AD4B000E5FCD /* Profile-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-staging"; - }; - 3C9C55382B07AD4B000E5FCD /* Profile-staging */ = { + 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] {{project_name.titleCase()}}"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.stg; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Profile-staging"; - }; - 3C9C55392B07AD4B000E5FCD /* Profile-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = {{application_id}}.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Profile-staging"; + name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C551B2B07AC99000E5FCD /* Debug-production */, - 3C9C55332B07AD26000E5FCD /* Debug-staging */, - 3C9C55272B07ACE7000E5FCD /* Debug-development */, - 3C9C551E2B07ACA4000E5FCD /* Release-production */, - 3C9C55362B07AD41000E5FCD /* Release-staging */, - 3C9C55302B07AD1B000E5FCD /* Release-development */, - 3C9C55242B07ACBD000E5FCD /* Profile-production */, - 3C9C55392B07AD4B000E5FCD /* Profile-staging */, - 3C9C552D2B07AD09000E5FCD /* Profile-development */, + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C55192B07AC99000E5FCD /* Debug-production */, - 3C9C55312B07AD26000E5FCD /* Debug-staging */, - 3C9C55252B07ACE7000E5FCD /* Debug-development */, - 3C9C551C2B07ACA4000E5FCD /* Release-production */, - 3C9C55342B07AD41000E5FCD /* Release-staging */, - 3C9C552E2B07AD1B000E5FCD /* Release-development */, - 3C9C55222B07ACBD000E5FCD /* Profile-production */, - 3C9C55372B07AD4B000E5FCD /* Profile-staging */, - 3C9C552B2B07AD09000E5FCD /* Profile-development */, + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C551A2B07AC99000E5FCD /* Debug-production */, - 3C9C55322B07AD26000E5FCD /* Debug-staging */, - 3C9C55262B07ACE7000E5FCD /* Debug-development */, - 3C9C551D2B07ACA4000E5FCD /* Release-production */, - 3C9C55352B07AD41000E5FCD /* Release-staging */, - 3C9C552F2B07AD1B000E5FCD /* Release-development */, - 3C9C55232B07ACBD000E5FCD /* Profile-production */, - 3C9C55382B07AD4B000E5FCD /* Profile-staging */, - 3C9C552C2B07AD09000E5FCD /* Profile-development */, + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..e3773d4 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme deleted file mode 100644 index 06da47c..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme deleted file mode 100644 index b59fb06..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme deleted file mode 100644 index 6e3b252..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/AppDelegate.swift b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/AppDelegate.swift index 70693e4..6266644 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/AppDelegate.swift +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Base.lproj/LaunchScreen.storyboard b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Base.lproj/LaunchScreen.storyboard index 6564da0..f2e259c 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,10 +1,8 @@ - - - + + - - + @@ -16,11 +14,9 @@ - - @@ -32,10 +28,10 @@ - + - + diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Info.plist b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Info.plist index 25236cf..fa50ea4 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Info.plist +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/ios/Runner/Info.plist @@ -2,15 +2,10 @@ - CFBundleLocalizations - - en - es - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - $(FLAVOR_APP_NAME) + Bricks CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -18,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(FLAVOR_APP_NAME) + bricks CFBundlePackageType APPL CFBundleShortVersionString @@ -46,8 +41,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/somnio_starter/hooks/post_gen.dart b/somnio_starter/hooks/post_gen.dart index 4a68465..4c39146 100644 --- a/somnio_starter/hooks/post_gen.dart +++ b/somnio_starter/hooks/post_gen.dart @@ -37,7 +37,12 @@ void run(HookContext context) async { progress = context.logger.progress( 'Creating auto generated assets using build_runner', ); - + await Process.run( + 'mkdir', + ['gen'], + runInShell: true, + workingDirectory: uiPackageDirectory, + ); await Process.run( 'flutter', ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'], diff --git a/todo/android/.gitignore b/todo/android/.gitignore index d6aa291..be3943c 100644 --- a/todo/android/.gitignore +++ b/todo/android/.gitignore @@ -1,11 +1,14 @@ gradle-wrapper.jar /.gradle /captures/ +/gradlew +/gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/todo/android/app/build.gradle b/todo/android/app/build.gradle deleted file mode 100644 index 86d1433..0000000 --- a/todo/android/app/build.gradle +++ /dev/null @@ -1,115 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - -android { - namespace "com.example.todo" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.todo" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - signingConfigs { - if (System.getenv("ANDROID_KEYSTORE_PATH")) { - release { - storeFile file(System.getenv("ANDROID_KEYSTORE_PATH")) - keyAlias System.getenv("ANDROID_KEYSTORE_ALIAS") - keyPassword System.getenv("ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD") - storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") - } - } else { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } - } - - flavorDimensions "default" - productFlavors { - production { - dimension "default" - applicationIdSuffix "" - manifestPlaceholders = [appName: "Todo"] - } - staging { - dimension "default" - applicationIdSuffix ".stg" - manifestPlaceholders = [appName: "[STG] Todo"] - } - development { - dimension "default" - applicationIdSuffix ".dev" - manifestPlaceholders = [appName: "[DEV] Todo"] - } - } - - buildTypes { - release { - signingConfig signingConfigs.release - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt') - } - debug { - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10" -} diff --git a/todo/android/app/build.gradle.kts b/todo/android/app/build.gradle.kts new file mode 100644 index 0000000..0202476 --- /dev/null +++ b/todo/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.bricks" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.bricks" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/todo/android/app/src/debug/AndroidManifest.xml b/todo/android/app/src/debug/AndroidManifest.xml index 42ff08b..399f698 100644 --- a/todo/android/app/src/debug/AndroidManifest.xml +++ b/todo/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + - - - - - - diff --git a/todo/android/app/src/development/res/drawable/launch_background.xml b/todo/android/app/src/development/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/todo/android/app/src/development/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/todo/android/app/src/main/AndroidManifest.xml b/todo/android/app/src/main/AndroidManifest.xml index 49db33d..29c379f 100644 --- a/todo/android/app/src/main/AndroidManifest.xml +++ b/todo/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + /> @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/todo/android/app/src/main/kotlin/com/example/MainActivity.kt b/todo/android/app/src/main/kotlin/com/example/MainActivity.kt deleted file mode 100644 index 9b5d2b3..0000000 --- a/todo/android/app/src/main/kotlin/com/example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.todo - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt b/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt new file mode 100644 index 0000000..ac09d0e --- /dev/null +++ b/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.bricks + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/todo/android/app/src/profile/AndroidManifest.xml b/todo/android/app/src/profile/AndroidManifest.xml index 42ff08b..399f698 100644 --- a/todo/android/app/src/profile/AndroidManifest.xml +++ b/todo/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + - - - - - - diff --git a/todo/android/app/src/staging/res/drawable/launch_background.xml b/todo/android/app/src/staging/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/todo/android/app/src/staging/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/todo/android/build.gradle b/todo/android/build.gradle deleted file mode 100644 index bc157bd..0000000 --- a/todo/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/todo/android/build.gradle.kts b/todo/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/todo/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/todo/android/gradle.properties b/todo/android/gradle.properties index 94adc3a..fbee1d8 100644 --- a/todo/android/gradle.properties +++ b/todo/android/gradle.properties @@ -1,3 +1,2 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true diff --git a/todo/android/gradle/wrapper/gradle-wrapper.properties b/todo/android/gradle/wrapper/gradle-wrapper.properties index 6b66533..e4ef43f 100644 --- a/todo/android/gradle/wrapper/gradle-wrapper.properties +++ b/todo/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/todo/android/settings.gradle b/todo/android/settings.gradle deleted file mode 100644 index 1d6d19b..0000000 --- a/todo/android/settings.gradle +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false -} - -include ":app" diff --git a/todo/android/settings.gradle.kts b/todo/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/todo/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/todo/ios/Flutter/AppFrameworkInfo.plist b/todo/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..1dc6cf7 100644 --- a/todo/ios/Flutter/AppFrameworkInfo.plist +++ b/todo/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/todo/ios/Runner.xcodeproj/project.pbxproj b/todo/ios/Runner.xcodeproj/project.pbxproj index f982601..42ac687 100644 --- a/todo/ios/Runner.xcodeproj/project.pbxproj +++ b/todo/ios/Runner.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -68,7 +68,7 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C807B294A618700263BE5 /* RunnerTests.swift */, @@ -124,7 +124,7 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { + 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( @@ -167,11 +167,11 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = NO; - LastUpgradeCheck = 1430; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { - 331C8080294A63A400263BE5 = { + 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; @@ -201,7 +201,7 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { + 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -256,7 +256,7 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { + 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -303,106 +303,11 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 3C9C55192B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Debug-production"; - }; - 3C9C551A2B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-production"; - }; - 3C9C551B2B07AC99000E5FCD /* Debug-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Debug-production"; - }; - 3C9C551C2B07ACA4000E5FCD /* Release-production */ = { + 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -432,6 +337,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -440,41 +346,38 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; - name = "Release-production"; + name = Profile; }; - 3C9C551D2B07ACA4000E5FCD /* Release-production */ = { + 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "Todo"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Release-production"; + name = Profile; }; - 3C9C551E2B07ACA4000E5FCD /* Release-production */ = { + 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -482,271 +385,16 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Release-production"; - }; - 3C9C55222B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-production"; - }; - 3C9C55232B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Profile-production"; - }; - 3C9C55242B07ACBD000E5FCD /* Profile-production */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Profile-production"; - }; - 3C9C55252B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = "Debug-development"; - }; - 3C9C55262B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-development"; - }; - 3C9C55272B07ACE7000E5FCD /* Debug-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Debug-development"; + name = Debug; }; - 3C9C552B2B07AD09000E5FCD /* Profile-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-development"; - }; - 3C9C552C2B07AD09000E5FCD /* Profile-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Profile-development"; - }; - 3C9C552D2B07AD09000E5FCD /* Profile-development */ = { + 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -754,89 +402,14 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Profile-development"; - }; - 3C9C552E2B07AD1B000E5FCD /* Release-development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = "Release-development"; + name = Release; }; - 3C9C552F2B07AD1B000E5FCD /* Release-development */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[DEV] Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Release-development"; - }; - 3C9C55302B07AD1B000E5FCD /* Release-development */ = { + 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -844,17 +417,18 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; }; - name = "Release-development"; + name = Profile; }; - 3C9C55312B07AD26000E5FCD /* Debug-staging */ = { + 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -884,6 +458,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -898,59 +473,19 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; - name = "Debug-staging"; - }; - 3C9C55322B07AD26000E5FCD /* Debug-staging */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] Todo"; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = "Debug-staging"; + name = Debug; }; - 3C9C55332B07AD26000E5FCD /* Debug-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Debug-staging"; - }; - 3C9C55342B07AD41000E5FCD /* Release-staging */ = { + 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -980,6 +515,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -988,7 +524,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -997,184 +533,85 @@ TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; - name = "Release-staging"; + name = Release; }; - 3C9C55352B07AD41000E5FCD /* Release-staging */ = { + 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] Todo"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Release-staging"; - }; - 3C9C55362B07AD41000E5FCD /* Release-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Release-staging"; + name = Debug; }; - 3C9C55372B07AD4B000E5FCD /* Profile-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = "Profile-staging"; - }; - 3C9C55382B07AD4B000E5FCD /* Profile-staging */ = { + 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-stg"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 332S4VAVCD; ENABLE_BITCODE = NO; - FLAVOR_APP_NAME = "[STG] Todo"; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PACKAGE_CONFIG = .dart_tool/package_config.json; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.stg; + PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; - name = "Profile-staging"; - }; - 3C9C55392B07AD4B000E5FCD /* Profile-staging */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.todo.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = "Profile-staging"; + name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C551B2B07AC99000E5FCD /* Debug-production */, - 3C9C55332B07AD26000E5FCD /* Debug-staging */, - 3C9C55272B07ACE7000E5FCD /* Debug-development */, - 3C9C551E2B07ACA4000E5FCD /* Release-production */, - 3C9C55362B07AD41000E5FCD /* Release-staging */, - 3C9C55302B07AD1B000E5FCD /* Release-development */, - 3C9C55242B07ACBD000E5FCD /* Profile-production */, - 3C9C55392B07AD4B000E5FCD /* Profile-staging */, - 3C9C552D2B07AD09000E5FCD /* Profile-development */, + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C55192B07AC99000E5FCD /* Debug-production */, - 3C9C55312B07AD26000E5FCD /* Debug-staging */, - 3C9C55252B07ACE7000E5FCD /* Debug-development */, - 3C9C551C2B07ACA4000E5FCD /* Release-production */, - 3C9C55342B07AD41000E5FCD /* Release-staging */, - 3C9C552E2B07AD1B000E5FCD /* Release-development */, - 3C9C55222B07ACBD000E5FCD /* Profile-production */, - 3C9C55372B07AD4B000E5FCD /* Profile-staging */, - 3C9C552B2B07AD09000E5FCD /* Profile-development */, + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3C9C551A2B07AC99000E5FCD /* Debug-production */, - 3C9C55322B07AD26000E5FCD /* Debug-staging */, - 3C9C55262B07ACE7000E5FCD /* Debug-development */, - 3C9C551D2B07ACA4000E5FCD /* Release-production */, - 3C9C55352B07AD41000E5FCD /* Release-staging */, - 3C9C552F2B07AD1B000E5FCD /* Release-development */, - 3C9C55232B07ACBD000E5FCD /* Profile-production */, - 3C9C55382B07AD4B000E5FCD /* Profile-staging */, - 3C9C552C2B07AD09000E5FCD /* Profile-development */, + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release-production"; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..e3773d4 100644 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme deleted file mode 100644 index 06da47c..0000000 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme deleted file mode 100644 index b59fb06..0000000 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme deleted file mode 100644 index 6e3b252..0000000 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner/AppDelegate.swift b/todo/ios/Runner/AppDelegate.swift index 70693e4..6266644 100644 --- a/todo/ios/Runner/AppDelegate.swift +++ b/todo/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon-stg.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard index 6564da0..f2e259c 100644 --- a/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,10 +1,8 @@ - - - + + - - + @@ -16,11 +14,9 @@ - - @@ -32,10 +28,10 @@ - + - + diff --git a/todo/ios/Runner/Info.plist b/todo/ios/Runner/Info.plist index 25236cf..fa50ea4 100644 --- a/todo/ios/Runner/Info.plist +++ b/todo/ios/Runner/Info.plist @@ -2,15 +2,10 @@ - CFBundleLocalizations - - en - es - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - $(FLAVOR_APP_NAME) + Bricks CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -18,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(FLAVOR_APP_NAME) + bricks CFBundlePackageType APPL CFBundleShortVersionString @@ -46,8 +41,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/todo/lib/l10n/l10n.dart b/todo/lib/l10n/l10n.dart index 17c891b..9bb5d4b 100644 --- a/todo/lib/l10n/l10n.dart +++ b/todo/lib/l10n/l10n.dart @@ -1,8 +1,7 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -export 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:todo/l10n/arb/app_localizations.dart'; +export 'package:todo/l10n/arb/app_localizations.dart'; extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); } diff --git a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart index 6aa0cc1..1488619 100644 --- a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart +++ b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart @@ -324,8 +324,8 @@ class UITheme { } /// Returns the correct [TabBarTheme] based on the current theme. - TabBarTheme get _tabBarTheme { - return TabBarTheme( + TabBarThemeData get _tabBarTheme { + return TabBarThemeData( labelStyle: UITextStyle.button, labelColor: UIColors.darkAqua, labelPadding: const EdgeInsets.symmetric( From 8abd67a984a32f7345b9817c86ba3f9823ee03ad Mon Sep 17 00:00:00 2001 From: MarcosTort Date: Wed, 10 Dec 2025 18:34:06 -0300 Subject: [PATCH 6/6] chore: delete the entire todo project codebase, including app, packages, tests, and platform configurations. --- .../gallery/pubspec.yaml | 1 - .../pubspec.yaml | 1 - todo/.github/PULL_REQUEST_TEMPLATE.md | 31 - todo/.github/cspell.json | 32 - todo/.github/dependabot.yaml | 18 - todo/.github/workflows/api_client.yaml | 20 - todo/.github/workflows/auth_client.yaml | 20 - todo/.github/workflows/form_inputs.yaml | 20 - todo/.github/workflows/main.yaml | 31 - todo/.github/workflows/todo_ui.yaml | 23 - todo/.github/workflows/token_provider.yaml | 20 - todo/.github/workflows/user_repository.yaml | 20 - todo/.gitignore | 117 ---- todo/.idea/runConfigurations/development.xml | 7 - todo/.idea/runConfigurations/production.xml | 7 - todo/.idea/runConfigurations/staging.xml | 7 - todo/.metadata | 33 - todo/.vscode/extensions.json | 9 - todo/.vscode/launch.json | 95 --- todo/README.md | 184 ------ todo/analysis_options.yaml | 8 - todo/android/.gitignore | 14 - todo/android/app/build.gradle.kts | 44 -- .../android/app/src/debug/AndroidManifest.xml | 7 - todo/android/app/src/main/AndroidManifest.xml | 45 -- .../kotlin/com/example/bricks/MainActivity.kt | 5 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - todo/android/build.gradle.kts | 24 - todo/android/gradle.properties | 2 - .../gradle/wrapper/gradle-wrapper.properties | 5 - todo/android/gradlew | 160 ----- todo/android/gradlew.bat | 90 --- todo/android/settings.gradle.kts | 26 - todo/app-keys.copy.json | 4 - todo/coverage_badge.svg | 20 - todo/ios/.gitignore | 34 - todo/ios/Flutter/AppFrameworkInfo.plist | 26 - todo/ios/Flutter/Debug.xcconfig | 2 - todo/ios/Flutter/Release.xcconfig | 2 - todo/ios/Podfile | 43 -- todo/ios/Runner.xcodeproj/project.pbxproj | 619 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 101 --- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - todo/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 -- todo/ios/Runner/Base.lproj/Main.storyboard | 26 - todo/ios/Runner/Info.plist | 49 -- todo/ios/Runner/Runner-Bridging-Header.h | 1 - todo/ios/RunnerTests/RunnerTests.swift | 12 - todo/l10n.yaml | 4 - todo/lib/app/app.dart | 4 - todo/lib/app/bloc/app_bloc.dart | 138 ---- todo/lib/app/bloc/app_event.dart | 44 -- todo/lib/app/bloc/app_state.dart | 74 --- todo/lib/app/constants/constants.dart | 1 - todo/lib/app/constants/navigation_keys.dart | 3 - todo/lib/app/extensions/build_context.dart | 8 - todo/lib/app/extensions/extensions.dart | 1 - todo/lib/app/router/app_router.dart | 101 --- todo/lib/app/router/auth_stream_scope.dart | 39 -- .../lib/app/router/navigation_bar_router.dart | 33 - todo/lib/app/router/router.dart | 3 - todo/lib/app/view/app.dart | 68 -- .../bloc/delete_account_bloc.dart | 84 --- .../bloc/delete_account_event.dart | 44 -- .../bloc/delete_account_state.dart | 51 -- .../delete_account/delete_account.dart | 2 - .../view/delete_account_page.dart | 26 - .../view/delete_account_view.dart | 112 ---- .../delete_account/view/view.dart | 2 - .../bloc/forgot_password_bloc.dart | 40 -- .../bloc/forgot_password_event.dart | 21 - .../bloc/forgot_password_state.dart | 49 -- .../forgot_password/forgot_password.dart | 2 - .../view/forgot_password_page.dart | 23 - .../view/forgot_password_view.dart | 106 --- .../forgot_password/view/view.dart | 2 - .../authentication/login/bloc/login_bloc.dart | 62 -- .../login/bloc/login_event.dart | 40 -- .../login/bloc/login_state.dart | 43 -- todo/lib/authentication/login/login.dart | 2 - .../authentication/login/view/login_page.dart | 23 - .../authentication/login/view/login_view.dart | 151 ----- todo/lib/authentication/login/view/view.dart | 2 - .../sign_up/bloc/sign_up_bloc.dart | 79 --- .../sign_up/bloc/sign_up_event.dart | 58 -- .../sign_up/bloc/sign_up_state.dart | 78 --- todo/lib/authentication/sign_up/sign_up.dart | 2 - .../sign_up/view/sign_up_page.dart | 22 - .../sign_up/view/sign_up_view.dart | 192 ------ .../lib/authentication/sign_up/view/view.dart | 2 - .../down_for_maintenance.dart | 2 - .../down_for_maintenance_page.dart | 16 - .../down_for_maintenance_view.dart | 38 -- todo/lib/force_upgrade/force_upgrade.dart | 2 - .../lib/force_upgrade/force_upgrade_page.dart | 16 - .../lib/force_upgrade/force_upgrade_view.dart | 39 -- todo/lib/home/bloc/home_bloc.dart | 13 - todo/lib/home/bloc/home_event.dart | 17 - todo/lib/home/bloc/home_state.dart | 29 - todo/lib/home/home.dart | 2 - todo/lib/home/view/home_page.dart | 22 - todo/lib/home/view/home_view.dart | 67 -- .../home/view/main_screen_page_builder.dart | 49 -- todo/lib/home/view/view.dart | 3 - todo/lib/l10n/arb/app_en.arb | 131 ---- todo/lib/l10n/arb/app_localizations.dart | 319 --------- todo/lib/l10n/arb/app_localizations_en.dart | 111 ---- todo/lib/l10n/l10n.dart | 7 - todo/lib/main/bootstrap.dart | 33 - todo/lib/main/main.dart | 1 - todo/lib/main/main_common.dart | 35 - todo/lib/main/main_development.dart | 14 - todo/lib/main/main_production.dart | 14 - todo/lib/main/main_staging.dart | 14 - todo/lib/navigation/navigation.dart | 1 - todo/lib/navigation/view/bottom_nav_bar.dart | 34 - todo/lib/navigation/view/view.dart | 1 - todo/lib/settings/bloc/settings_bloc.dart | 68 -- todo/lib/settings/bloc/settings_event.dart | 20 - todo/lib/settings/bloc/settings_state.dart | 40 -- todo/lib/settings/settings.dart | 2 - todo/lib/settings/view/settings_page.dart | 25 - todo/lib/settings/view/settings_view.dart | 111 ---- todo/lib/settings/view/view.dart | 2 - todo/packages/api_client/.gitignore | 7 - .../packages/api_client/analysis_options.yaml | 4 - todo/packages/api_client/lib/api_client.dart | 7 - .../api_client/lib/src/api_client.dart | 27 - .../api_client/lib/src/http_api_client.dart | 232 ------- .../src/resources/app_config/app_config.dart | 3 - .../app_config/app_config_resource.dart | 62 -- .../app_config/exceptions/exceptions.dart | 31 - .../resources/app_config/models/models.dart | 1 - .../resources/app_config/models/upgrade.dart | 46 -- .../app_config/models/upgrade.g.dart | 21 - .../lib/src/resources/resources.dart | 2 - .../resources/user/exceptions/exceptions.dart | 71 -- .../lib/src/resources/user/models/models.dart | 4 - .../user/models/sign_up_request.dart | 39 -- .../user/models/sign_up_request.g.dart | 21 - .../user/models/update_password_request.dart | 39 -- .../models/update_password_request.g.dart | 23 - .../src/resources/user/models/user_data.dart | 43 -- .../resources/user/models/user_data.g.dart | 19 - .../user/models/verification_request.dart | 35 - .../user/models/verification_request.g.dart | 21 - .../lib/src/resources/user/user.dart | 3 - .../lib/src/resources/user/user_resource.dart | 173 ----- .../api_client/lib/src/utils/utils.dart | 8 - todo/packages/api_client/pubspec.yaml | 23 - .../api_client/test/src/api_client_test.dart | 31 - .../test/src/http_api_client_test.dart | 498 -------------- .../app_config/app_config_resources_test.dart | 73 --- .../exceptions/exceptions_test.dart | 43 -- .../app_config/models/upgrade_test.dart | 44 -- .../user/exceptions/exceptions_test.dart | 52 -- .../user/models/sign_up_requested_test.dart | 54 -- .../update_password_requested_test.dart | 54 -- .../resources/user/models/user_data_test.dart | 54 -- .../models/verification_request_test.dart | 37 -- .../resources/user/user_resources_test.dart | 458 ------------- .../api_client/test/src/utils/utils_test.dart | 22 - .../analysis_options.yaml | 4 - .../packages/app_config_repository/build.yaml | 12 - .../lib/app_config_repository.dart | 5 - .../lib/src/app_config_repository.dart | 54 -- .../lib/src/models/app_config.dart | 49 -- .../lib/src/models/app_config.g.dart | 33 - .../lib/src/models/force_upgrade.dart | 41 -- .../lib/src/models/models.dart | 2 - .../app_config_repository/pubspec.yaml | 21 - .../test/app_config_repository_test.dart | 87 --- .../test/models/app_config_test.dart | 86 --- .../test/models/force_upgrade_test.dart | 53 -- todo/packages/auth_client/.gitignore | 4 - .../auth_client/analysis_options.yaml | 4 - .../packages/auth_client/lib/auth_client.dart | 6 - .../auth_client/lib/src/auth_client.dart | 97 --- .../lib/src/exceptions/exceptions.dart | 63 -- .../lib/src/models/authentication_event.dart | 52 -- .../lib/src/models/authentication_user.dart | 30 - .../auth_client/lib/src/models/models.dart | 2 - todo/packages/auth_client/pubspec.yaml | 18 - .../test/src/auth_client_test.dart | 74 --- .../test/src/exceptions/exceptions_test.dart | 52 -- .../src/models/authentication_event_test.dart | 82 --- .../src/models/authentication_user_test.dart | 54 -- todo/packages/form_inputs/.gitignore | 39 -- todo/packages/form_inputs/LICENSE | 19 - todo/packages/form_inputs/README.md | 3 - .../form_inputs/analysis_options.yaml | 4 - .../packages/form_inputs/lib/form_inputs.dart | 11 - .../form_inputs/lib/src/address_label.dart | 34 - todo/packages/form_inputs/lib/src/email.dart | 45 -- .../form_inputs/lib/src/form_status_enum.dart | 20 - todo/packages/form_inputs/lib/src/name.dart | 40 -- .../form_inputs/lib/src/otp_code.dart | 31 - .../form_inputs/lib/src/password.dart | 69 -- .../form_inputs/lib/src/phone_number.dart | 40 -- todo/packages/form_inputs/lib/src/src.dart | 8 - .../form_inputs/lib/src/username.dart | 42 -- .../packages/form_inputs/lib/src/zipcode.dart | 48 -- todo/packages/form_inputs/pubspec.yaml | 14 - .../form_inputs/test/address_label_test.dart | 44 -- .../packages/form_inputs/test/email_test.dart | 60 -- todo/packages/form_inputs/test/name_test.dart | 44 -- .../form_inputs/test/otp_code_test.dart | 44 -- .../form_inputs/test/password_test.dart | 45 -- .../form_inputs/test/phone_number_test.dart | 44 -- .../form_inputs/test/username_test.dart | 44 -- .../form_inputs/test/zipcode_test.dart | 45 -- todo/packages/todo_ui/.gitignore | 5 - todo/packages/todo_ui/README.md | 121 ---- todo/packages/todo_ui/analysis_options.yaml | 4 - .../assets/fonts/NotoSansDisplay-Regular.ttf | Bin 113460 -> 0 bytes .../assets/fonts/NotoSansDisplay-SemiBold.ttf | Bin 113440 -> 0 bytes .../todo_ui/assets/icons/back_icon.svg | 3 - .../todo_ui/assets/icons/email_outline.svg | 3 - todo/packages/todo_ui/assets/images/icon.png | Bin 544 -> 0 bytes todo/packages/todo_ui/gallery/.gitignore | 41 -- todo/packages/todo_ui/gallery/.metadata | 10 - todo/packages/todo_ui/gallery/README.md | 9 - .../todo_ui/gallery/analysis_options.yaml | 5 - .../todo_ui/gallery/android/.gitignore | 13 - .../todo_ui/gallery/android/app/build.gradle | 68 -- .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 34 - .../kotlin/com/ui/gallery/MainActivity.kt | 6 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - .../todo_ui/gallery/android/build.gradle | 31 - .../todo_ui/gallery/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 6 - .../todo_ui/gallery/android/settings.gradle | 11 - todo/packages/todo_ui/gallery/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 26 - .../gallery/ios/Flutter/Debug.xcconfig | 2 - .../gallery/ios/Flutter/Release.xcconfig | 2 - todo/packages/todo_ui/gallery/ios/Podfile | 41 -- .../ios/Runner.xcodeproj/project.pbxproj | 481 -------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 87 --- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../gallery/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 ---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 564 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 1283 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1588 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 1025 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 1716 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1920 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 1283 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1895 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 2665 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 2665 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 3831 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1888 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 3294 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 3612 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 -- .../ios/Runner/Base.lproj/Main.storyboard | 26 - .../todo_ui/gallery/ios/Runner/Info.plist | 45 -- .../ios/Runner/Runner-Bridging-Header.h | 1 - .../todo_ui/gallery/lib/colors/colors.dart | 1 - .../gallery/lib/colors/colors_page.dart | 179 ----- todo/packages/todo_ui/gallery/lib/main.dart | 129 ---- .../todo_ui/gallery/lib/spacing/spacing.dart | 1 - .../gallery/lib/spacing/spacing_page.dart | 73 --- .../gallery/lib/typography/typography.dart | 1 - .../lib/typography/typography_page.dart | 62 -- .../gallery/lib/widgets/app_bar_page.dart | 45 -- .../gallery/lib/widgets/button_page.dart | 120 ---- .../gallery/lib/widgets/icons_page.dart | 46 -- .../gallery/lib/widgets/text_field_page.dart | 40 -- .../todo_ui/gallery/lib/widgets/widgets.dart | 5 - .../gallery/lib/widgets/widgets_page.dart | 80 --- todo/packages/todo_ui/gallery/pubspec.yaml | 20 - todo/packages/todo_ui/gallery/web/favicon.png | Bin 917 -> 0 bytes .../todo_ui/gallery/web/icons/Icon-192.png | Bin 840 -> 0 bytes .../todo_ui/gallery/web/icons/Icon-512.png | Bin 2172 -> 0 bytes .../gallery/web/icons/Icon-maskable-192.png | Bin 1280 -> 0 bytes .../gallery/web/icons/Icon-maskable-512.png | Bin 3871 -> 0 bytes todo/packages/todo_ui/gallery/web/index.html | 104 --- .../todo_ui/gallery/web/manifest.json | 35 - .../todo_ui/lib/src/colors/todo_colors.dart | 93 --- .../lib/src/extensions/extensions.dart | 1 - .../lib/src/extensions/theme_data.dart | 15 - .../packages/todo_ui/lib/src/icons/icons.dart | 3 - .../todo_ui/lib/src/icons/todo_icons.dart | 26 - .../lib/src/icons/todo_icons_dark.dart | 22 - .../lib/src/icons/todo_icons_light.dart | 22 - .../todo_ui/lib/src/spacing/todo_spacing.dart | 38 -- .../todo_ui/lib/src/theme/todo_theme.dart | 426 ------------ .../lib/src/typography/todo_font_weight.dart | 31 - .../lib/src/typography/todo_text_styles.dart | 106 --- .../lib/src/typography/typography.dart | 2 - .../todo_ui/lib/src/widgets/todo_app_bar.dart | 120 ---- .../lib/src/widgets/todo_outlined_button.dart | 110 ---- .../lib/src/widgets/todo_text_field.dart | 169 ----- .../todo_ui/lib/src/widgets/widgets.dart | 3 - todo/packages/todo_ui/lib/todo_ui.dart | 7 - todo/packages/todo_ui/pubspec.yaml | 41 -- .../test/src/extensions/theme_data_test.dart | 17 - .../todo_ui/test/src/helpers/helpers.dart | 1 - .../todo_ui/test/src/helpers/pump_app.dart | 19 - .../test/src/icons/todo_icons_dark_test.dart | 21 - .../test/src/icons/todo_icons_light_test.dart | 21 - .../test/src/icons/todo_icons_test.dart | 23 - .../src/typography/todo_text_styles_test.dart | 61 -- .../test/src/widgets/todo_app_bar_test.dart | 44 -- .../widgets/todo_outlined_button_test.dart | 125 ---- .../src/widgets/todo_text_field_test.dart | 58 -- .../todo_ui/test/theme/todo_theme_test.dart | 324 --------- todo/packages/token_provider/.gitignore | 7 - .../token_provider/analysis_options.yaml | 4 - .../lib/src/token_provider.dart | 14 - .../token_provider/lib/token_provider.dart | 4 - todo/packages/token_provider/pubspec.yaml | 12 - .../test/src/token_provider_test.dart | 22 - todo/packages/user_repository/.gitignore | 7 - .../user_repository/analysis_options.yaml | 4 - .../lib/src/exceptions/exceptions.dart | 39 -- .../lib/src/models/models.dart | 1 - .../user_repository/lib/src/models/user.dart | 38 -- .../lib/src/models/user.g.dart | 17 - .../lib/src/user_repository.dart | 243 ------- .../user_repository/lib/user_repository.dart | 6 - todo/packages/user_repository/pubspec.yaml | 22 - .../test/src/exceptions/exceptions_test.dart | 36 - .../test/src/models/user_test.dart | 48 -- .../test/src/user_repository_test.dart | 546 --------------- todo/pubspec.yaml | 44 -- todo/test/app/bloc/app_bloc_test.dart | 262 -------- todo/test/app/bloc/app_event_test.dart | 53 -- todo/test/app/bloc/app_state_test.dart | 37 -- .../app/extensions/build_context_test.dart | 51 -- todo/test/app/view/app_view_test.dart | 72 -- .../bloc/delete_account_bloc_test.dart | 163 ----- .../bloc/delete_account_event_test.dart | 60 -- .../view/delete_account_page_test.dart | 38 -- .../view/delete_account_view_test.dart | 86 --- .../bloc/forgot_password_bloc_test.dart | 76 --- .../bloc/forgot_password_event_test.dart | 36 - .../bloc/forgot_password_state_test.dart | 120 ---- .../view/forgot_password_view_test.dart | 137 ---- .../login/bloc/login_bloc_test.dart | 106 --- .../login/bloc/login_event_test.dart | 70 -- .../login/view/forgot_password_page_test.dart | 38 -- .../login/view/login_view_test.dart | 255 -------- .../sign_up/bloc/sign_up_bloc_test.dart | 141 ---- .../sign_up/bloc/sign_up_event_test.dart | 108 --- .../sign_up/bloc/sign_up_state_test.dart | 85 --- .../sign_up/view/sign_up_page_test.dart | 38 -- .../sign_up/view/sign_up_view_test.dart | 399 ----------- .../down_for_maintenance_page_test.dart | 39 -- .../down_for_maintenance_view_test.dart | 44 -- .../force_upgrade_page_test.dart | 38 -- .../force_upgrade_view_test.dart | 69 -- todo/test/helpers/helpers.dart | 1 - todo/test/helpers/pump_app.dart | 139 ---- todo/test/home/bloc/home_bloc_test.dart | 13 - todo/test/home/bloc/home_event_test.dart | 12 - todo/test/home/bloc/home_state_test.dart | 23 - todo/test/home/view/home_page_test.dart | 38 -- todo/test/home/view/home_view_test.dart | 105 --- .../settings/bloc/settings_bloc_test.dart | 26 - .../settings/bloc/settings_event_test.dart | 22 - .../settings/bloc/settings_state_test.dart | 32 - .../settings/view/settings_view_test.dart | 107 --- todo/web/favicon.png | Bin 917 -> 0 bytes todo/web/icons/Icon-192.png | Bin 5292 -> 0 bytes todo/web/icons/Icon-512.png | Bin 8252 -> 0 bytes todo/web/icons/Icon-maskable-192.png | Bin 5594 -> 0 bytes todo/web/icons/Icon-maskable-512.png | Bin 20998 -> 0 bytes todo/web/index.html | 50 -- todo/web/manifest.json | 23 - .../gallery/pubspec.yaml | 1 - .../pubspec.yaml | 5 +- 432 files changed, 2 insertions(+), 18382 deletions(-) delete mode 100644 todo/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 todo/.github/cspell.json delete mode 100644 todo/.github/dependabot.yaml delete mode 100644 todo/.github/workflows/api_client.yaml delete mode 100644 todo/.github/workflows/auth_client.yaml delete mode 100644 todo/.github/workflows/form_inputs.yaml delete mode 100644 todo/.github/workflows/main.yaml delete mode 100644 todo/.github/workflows/todo_ui.yaml delete mode 100644 todo/.github/workflows/token_provider.yaml delete mode 100644 todo/.github/workflows/user_repository.yaml delete mode 100644 todo/.gitignore delete mode 100644 todo/.idea/runConfigurations/development.xml delete mode 100644 todo/.idea/runConfigurations/production.xml delete mode 100644 todo/.idea/runConfigurations/staging.xml delete mode 100644 todo/.metadata delete mode 100644 todo/.vscode/extensions.json delete mode 100644 todo/.vscode/launch.json delete mode 100644 todo/README.md delete mode 100644 todo/analysis_options.yaml delete mode 100644 todo/android/.gitignore delete mode 100644 todo/android/app/build.gradle.kts delete mode 100644 todo/android/app/src/debug/AndroidManifest.xml delete mode 100644 todo/android/app/src/main/AndroidManifest.xml delete mode 100644 todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt delete mode 100644 todo/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 todo/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 todo/android/app/src/main/res/values-night/styles.xml delete mode 100644 todo/android/app/src/main/res/values/styles.xml delete mode 100644 todo/android/app/src/profile/AndroidManifest.xml delete mode 100644 todo/android/build.gradle.kts delete mode 100644 todo/android/gradle.properties delete mode 100644 todo/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 todo/android/gradlew delete mode 100644 todo/android/gradlew.bat delete mode 100644 todo/android/settings.gradle.kts delete mode 100644 todo/app-keys.copy.json delete mode 100644 todo/coverage_badge.svg delete mode 100644 todo/ios/.gitignore delete mode 100644 todo/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 todo/ios/Flutter/Debug.xcconfig delete mode 100644 todo/ios/Flutter/Release.xcconfig delete mode 100644 todo/ios/Podfile delete mode 100644 todo/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 todo/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 todo/ios/Runner/AppDelegate.swift delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 todo/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 todo/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 todo/ios/Runner/Info.plist delete mode 100644 todo/ios/Runner/Runner-Bridging-Header.h delete mode 100644 todo/ios/RunnerTests/RunnerTests.swift delete mode 100644 todo/l10n.yaml delete mode 100644 todo/lib/app/app.dart delete mode 100644 todo/lib/app/bloc/app_bloc.dart delete mode 100644 todo/lib/app/bloc/app_event.dart delete mode 100644 todo/lib/app/bloc/app_state.dart delete mode 100644 todo/lib/app/constants/constants.dart delete mode 100644 todo/lib/app/constants/navigation_keys.dart delete mode 100644 todo/lib/app/extensions/build_context.dart delete mode 100644 todo/lib/app/extensions/extensions.dart delete mode 100644 todo/lib/app/router/app_router.dart delete mode 100644 todo/lib/app/router/auth_stream_scope.dart delete mode 100644 todo/lib/app/router/navigation_bar_router.dart delete mode 100644 todo/lib/app/router/router.dart delete mode 100644 todo/lib/app/view/app.dart delete mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart delete mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_event.dart delete mode 100644 todo/lib/authentication/delete_account/bloc/delete_account_state.dart delete mode 100644 todo/lib/authentication/delete_account/delete_account.dart delete mode 100644 todo/lib/authentication/delete_account/view/delete_account_page.dart delete mode 100644 todo/lib/authentication/delete_account/view/delete_account_view.dart delete mode 100644 todo/lib/authentication/delete_account/view/view.dart delete mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart delete mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart delete mode 100644 todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart delete mode 100644 todo/lib/authentication/forgot_password/forgot_password.dart delete mode 100644 todo/lib/authentication/forgot_password/view/forgot_password_page.dart delete mode 100644 todo/lib/authentication/forgot_password/view/forgot_password_view.dart delete mode 100644 todo/lib/authentication/forgot_password/view/view.dart delete mode 100644 todo/lib/authentication/login/bloc/login_bloc.dart delete mode 100644 todo/lib/authentication/login/bloc/login_event.dart delete mode 100644 todo/lib/authentication/login/bloc/login_state.dart delete mode 100644 todo/lib/authentication/login/login.dart delete mode 100644 todo/lib/authentication/login/view/login_page.dart delete mode 100644 todo/lib/authentication/login/view/login_view.dart delete mode 100644 todo/lib/authentication/login/view/view.dart delete mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart delete mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_event.dart delete mode 100644 todo/lib/authentication/sign_up/bloc/sign_up_state.dart delete mode 100644 todo/lib/authentication/sign_up/sign_up.dart delete mode 100644 todo/lib/authentication/sign_up/view/sign_up_page.dart delete mode 100644 todo/lib/authentication/sign_up/view/sign_up_view.dart delete mode 100644 todo/lib/authentication/sign_up/view/view.dart delete mode 100644 todo/lib/down_for_maintenance/down_for_maintenance.dart delete mode 100644 todo/lib/down_for_maintenance/down_for_maintenance_page.dart delete mode 100644 todo/lib/down_for_maintenance/down_for_maintenance_view.dart delete mode 100644 todo/lib/force_upgrade/force_upgrade.dart delete mode 100644 todo/lib/force_upgrade/force_upgrade_page.dart delete mode 100644 todo/lib/force_upgrade/force_upgrade_view.dart delete mode 100644 todo/lib/home/bloc/home_bloc.dart delete mode 100644 todo/lib/home/bloc/home_event.dart delete mode 100644 todo/lib/home/bloc/home_state.dart delete mode 100644 todo/lib/home/home.dart delete mode 100644 todo/lib/home/view/home_page.dart delete mode 100644 todo/lib/home/view/home_view.dart delete mode 100644 todo/lib/home/view/main_screen_page_builder.dart delete mode 100644 todo/lib/home/view/view.dart delete mode 100644 todo/lib/l10n/arb/app_en.arb delete mode 100644 todo/lib/l10n/arb/app_localizations.dart delete mode 100644 todo/lib/l10n/arb/app_localizations_en.dart delete mode 100644 todo/lib/l10n/l10n.dart delete mode 100644 todo/lib/main/bootstrap.dart delete mode 100644 todo/lib/main/main.dart delete mode 100644 todo/lib/main/main_common.dart delete mode 100644 todo/lib/main/main_development.dart delete mode 100644 todo/lib/main/main_production.dart delete mode 100644 todo/lib/main/main_staging.dart delete mode 100644 todo/lib/navigation/navigation.dart delete mode 100644 todo/lib/navigation/view/bottom_nav_bar.dart delete mode 100644 todo/lib/navigation/view/view.dart delete mode 100644 todo/lib/settings/bloc/settings_bloc.dart delete mode 100644 todo/lib/settings/bloc/settings_event.dart delete mode 100644 todo/lib/settings/bloc/settings_state.dart delete mode 100644 todo/lib/settings/settings.dart delete mode 100644 todo/lib/settings/view/settings_page.dart delete mode 100644 todo/lib/settings/view/settings_view.dart delete mode 100644 todo/lib/settings/view/view.dart delete mode 100644 todo/packages/api_client/.gitignore delete mode 100644 todo/packages/api_client/analysis_options.yaml delete mode 100644 todo/packages/api_client/lib/api_client.dart delete mode 100644 todo/packages/api_client/lib/src/api_client.dart delete mode 100644 todo/packages/api_client/lib/src/http_api_client.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/app_config.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/models.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart delete mode 100644 todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart delete mode 100644 todo/packages/api_client/lib/src/resources/resources.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/models.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/user_data.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/verification_request.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/user.dart delete mode 100644 todo/packages/api_client/lib/src/resources/user/user_resource.dart delete mode 100644 todo/packages/api_client/lib/src/utils/utils.dart delete mode 100644 todo/packages/api_client/pubspec.yaml delete mode 100644 todo/packages/api_client/test/src/api_client_test.dart delete mode 100644 todo/packages/api_client/test/src/http_api_client_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/models/user_data_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart delete mode 100644 todo/packages/api_client/test/src/resources/user/user_resources_test.dart delete mode 100644 todo/packages/api_client/test/src/utils/utils_test.dart delete mode 100644 todo/packages/app_config_repository/analysis_options.yaml delete mode 100644 todo/packages/app_config_repository/build.yaml delete mode 100644 todo/packages/app_config_repository/lib/app_config_repository.dart delete mode 100644 todo/packages/app_config_repository/lib/src/app_config_repository.dart delete mode 100644 todo/packages/app_config_repository/lib/src/models/app_config.dart delete mode 100644 todo/packages/app_config_repository/lib/src/models/app_config.g.dart delete mode 100644 todo/packages/app_config_repository/lib/src/models/force_upgrade.dart delete mode 100644 todo/packages/app_config_repository/lib/src/models/models.dart delete mode 100644 todo/packages/app_config_repository/pubspec.yaml delete mode 100644 todo/packages/app_config_repository/test/app_config_repository_test.dart delete mode 100644 todo/packages/app_config_repository/test/models/app_config_test.dart delete mode 100644 todo/packages/app_config_repository/test/models/force_upgrade_test.dart delete mode 100644 todo/packages/auth_client/.gitignore delete mode 100644 todo/packages/auth_client/analysis_options.yaml delete mode 100644 todo/packages/auth_client/lib/auth_client.dart delete mode 100644 todo/packages/auth_client/lib/src/auth_client.dart delete mode 100644 todo/packages/auth_client/lib/src/exceptions/exceptions.dart delete mode 100644 todo/packages/auth_client/lib/src/models/authentication_event.dart delete mode 100644 todo/packages/auth_client/lib/src/models/authentication_user.dart delete mode 100644 todo/packages/auth_client/lib/src/models/models.dart delete mode 100644 todo/packages/auth_client/pubspec.yaml delete mode 100644 todo/packages/auth_client/test/src/auth_client_test.dart delete mode 100644 todo/packages/auth_client/test/src/exceptions/exceptions_test.dart delete mode 100644 todo/packages/auth_client/test/src/models/authentication_event_test.dart delete mode 100644 todo/packages/auth_client/test/src/models/authentication_user_test.dart delete mode 100644 todo/packages/form_inputs/.gitignore delete mode 100644 todo/packages/form_inputs/LICENSE delete mode 100644 todo/packages/form_inputs/README.md delete mode 100644 todo/packages/form_inputs/analysis_options.yaml delete mode 100644 todo/packages/form_inputs/lib/form_inputs.dart delete mode 100644 todo/packages/form_inputs/lib/src/address_label.dart delete mode 100644 todo/packages/form_inputs/lib/src/email.dart delete mode 100644 todo/packages/form_inputs/lib/src/form_status_enum.dart delete mode 100644 todo/packages/form_inputs/lib/src/name.dart delete mode 100644 todo/packages/form_inputs/lib/src/otp_code.dart delete mode 100644 todo/packages/form_inputs/lib/src/password.dart delete mode 100644 todo/packages/form_inputs/lib/src/phone_number.dart delete mode 100644 todo/packages/form_inputs/lib/src/src.dart delete mode 100644 todo/packages/form_inputs/lib/src/username.dart delete mode 100644 todo/packages/form_inputs/lib/src/zipcode.dart delete mode 100644 todo/packages/form_inputs/pubspec.yaml delete mode 100644 todo/packages/form_inputs/test/address_label_test.dart delete mode 100644 todo/packages/form_inputs/test/email_test.dart delete mode 100644 todo/packages/form_inputs/test/name_test.dart delete mode 100644 todo/packages/form_inputs/test/otp_code_test.dart delete mode 100644 todo/packages/form_inputs/test/password_test.dart delete mode 100644 todo/packages/form_inputs/test/phone_number_test.dart delete mode 100644 todo/packages/form_inputs/test/username_test.dart delete mode 100644 todo/packages/form_inputs/test/zipcode_test.dart delete mode 100644 todo/packages/todo_ui/.gitignore delete mode 100644 todo/packages/todo_ui/README.md delete mode 100644 todo/packages/todo_ui/analysis_options.yaml delete mode 100644 todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf delete mode 100644 todo/packages/todo_ui/assets/fonts/NotoSansDisplay-SemiBold.ttf delete mode 100644 todo/packages/todo_ui/assets/icons/back_icon.svg delete mode 100644 todo/packages/todo_ui/assets/icons/email_outline.svg delete mode 100644 todo/packages/todo_ui/assets/images/icon.png delete mode 100644 todo/packages/todo_ui/gallery/.gitignore delete mode 100644 todo/packages/todo_ui/gallery/.metadata delete mode 100644 todo/packages/todo_ui/gallery/README.md delete mode 100644 todo/packages/todo_ui/gallery/analysis_options.yaml delete mode 100644 todo/packages/todo_ui/gallery/android/.gitignore delete mode 100644 todo/packages/todo_ui/gallery/android/app/build.gradle delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml delete mode 100644 todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml delete mode 100644 todo/packages/todo_ui/gallery/android/build.gradle delete mode 100644 todo/packages/todo_ui/gallery/android/gradle.properties delete mode 100644 todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 todo/packages/todo_ui/gallery/android/settings.gradle delete mode 100644 todo/packages/todo_ui/gallery/ios/.gitignore delete mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig delete mode 100644 todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig delete mode 100644 todo/packages/todo_ui/gallery/ios/Podfile delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Info.plist delete mode 100644 todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h delete mode 100644 todo/packages/todo_ui/gallery/lib/colors/colors.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/colors/colors_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/main.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/spacing/spacing.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/typography/typography.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/typography/typography_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/button_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/widgets.dart delete mode 100644 todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart delete mode 100644 todo/packages/todo_ui/gallery/pubspec.yaml delete mode 100644 todo/packages/todo_ui/gallery/web/favicon.png delete mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-192.png delete mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-512.png delete mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-maskable-192.png delete mode 100644 todo/packages/todo_ui/gallery/web/icons/Icon-maskable-512.png delete mode 100644 todo/packages/todo_ui/gallery/web/index.html delete mode 100644 todo/packages/todo_ui/gallery/web/manifest.json delete mode 100644 todo/packages/todo_ui/lib/src/colors/todo_colors.dart delete mode 100644 todo/packages/todo_ui/lib/src/extensions/extensions.dart delete mode 100644 todo/packages/todo_ui/lib/src/extensions/theme_data.dart delete mode 100644 todo/packages/todo_ui/lib/src/icons/icons.dart delete mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons.dart delete mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart delete mode 100644 todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart delete mode 100644 todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart delete mode 100644 todo/packages/todo_ui/lib/src/theme/todo_theme.dart delete mode 100644 todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart delete mode 100644 todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart delete mode 100644 todo/packages/todo_ui/lib/src/typography/typography.dart delete mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart delete mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart delete mode 100644 todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart delete mode 100644 todo/packages/todo_ui/lib/src/widgets/widgets.dart delete mode 100644 todo/packages/todo_ui/lib/todo_ui.dart delete mode 100644 todo/packages/todo_ui/pubspec.yaml delete mode 100644 todo/packages/todo_ui/test/src/extensions/theme_data_test.dart delete mode 100644 todo/packages/todo_ui/test/src/helpers/helpers.dart delete mode 100644 todo/packages/todo_ui/test/src/helpers/pump_app.dart delete mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart delete mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart delete mode 100644 todo/packages/todo_ui/test/src/icons/todo_icons_test.dart delete mode 100644 todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart delete mode 100644 todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart delete mode 100644 todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart delete mode 100644 todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart delete mode 100644 todo/packages/todo_ui/test/theme/todo_theme_test.dart delete mode 100644 todo/packages/token_provider/.gitignore delete mode 100644 todo/packages/token_provider/analysis_options.yaml delete mode 100644 todo/packages/token_provider/lib/src/token_provider.dart delete mode 100644 todo/packages/token_provider/lib/token_provider.dart delete mode 100644 todo/packages/token_provider/pubspec.yaml delete mode 100644 todo/packages/token_provider/test/src/token_provider_test.dart delete mode 100644 todo/packages/user_repository/.gitignore delete mode 100644 todo/packages/user_repository/analysis_options.yaml delete mode 100644 todo/packages/user_repository/lib/src/exceptions/exceptions.dart delete mode 100644 todo/packages/user_repository/lib/src/models/models.dart delete mode 100644 todo/packages/user_repository/lib/src/models/user.dart delete mode 100644 todo/packages/user_repository/lib/src/models/user.g.dart delete mode 100644 todo/packages/user_repository/lib/src/user_repository.dart delete mode 100644 todo/packages/user_repository/lib/user_repository.dart delete mode 100644 todo/packages/user_repository/pubspec.yaml delete mode 100644 todo/packages/user_repository/test/src/exceptions/exceptions_test.dart delete mode 100644 todo/packages/user_repository/test/src/models/user_test.dart delete mode 100644 todo/packages/user_repository/test/src/user_repository_test.dart delete mode 100644 todo/pubspec.yaml delete mode 100644 todo/test/app/bloc/app_bloc_test.dart delete mode 100644 todo/test/app/bloc/app_event_test.dart delete mode 100644 todo/test/app/bloc/app_state_test.dart delete mode 100644 todo/test/app/extensions/build_context_test.dart delete mode 100644 todo/test/app/view/app_view_test.dart delete mode 100644 todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart delete mode 100644 todo/test/authentication/delete_account/bloc/delete_account_event_test.dart delete mode 100644 todo/test/authentication/delete_account/view/delete_account_page_test.dart delete mode 100644 todo/test/authentication/delete_account/view/delete_account_view_test.dart delete mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart delete mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart delete mode 100644 todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart delete mode 100644 todo/test/authentication/forgot_password/view/forgot_password_view_test.dart delete mode 100644 todo/test/authentication/login/bloc/login_bloc_test.dart delete mode 100644 todo/test/authentication/login/bloc/login_event_test.dart delete mode 100644 todo/test/authentication/login/view/forgot_password_page_test.dart delete mode 100644 todo/test/authentication/login/view/login_view_test.dart delete mode 100644 todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart delete mode 100644 todo/test/authentication/sign_up/bloc/sign_up_event_test.dart delete mode 100644 todo/test/authentication/sign_up/bloc/sign_up_state_test.dart delete mode 100644 todo/test/authentication/sign_up/view/sign_up_page_test.dart delete mode 100644 todo/test/authentication/sign_up/view/sign_up_view_test.dart delete mode 100644 todo/test/down_for_maintenance/down_for_maintenance_page_test.dart delete mode 100644 todo/test/down_for_maintenance/down_for_maintenance_view_test.dart delete mode 100644 todo/test/force_upgrade/force_upgrade_page_test.dart delete mode 100644 todo/test/force_upgrade/force_upgrade_view_test.dart delete mode 100644 todo/test/helpers/helpers.dart delete mode 100644 todo/test/helpers/pump_app.dart delete mode 100644 todo/test/home/bloc/home_bloc_test.dart delete mode 100644 todo/test/home/bloc/home_event_test.dart delete mode 100644 todo/test/home/bloc/home_state_test.dart delete mode 100644 todo/test/home/view/home_page_test.dart delete mode 100644 todo/test/home/view/home_view_test.dart delete mode 100644 todo/test/settings/bloc/settings_bloc_test.dart delete mode 100644 todo/test/settings/bloc/settings_event_test.dart delete mode 100644 todo/test/settings/bloc/settings_state_test.dart delete mode 100644 todo/test/settings/view/settings_view_test.dart delete mode 100644 todo/web/favicon.png delete mode 100644 todo/web/icons/Icon-192.png delete mode 100644 todo/web/icons/Icon-512.png delete mode 100644 todo/web/icons/Icon-maskable-192.png delete mode 100644 todo/web/icons/Icon-maskable-512.png delete mode 100644 todo/web/index.html delete mode 100644 todo/web/manifest.json diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml index 93a21c1..9f2ad61 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/gallery/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: flutter: sdk: flutter - flutter_asset_generator: ^1.0.2 {{project_name.snakeCase()}}_ui: path: ../ diff --git a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml index 84e1a7c..d64332c 100644 --- a/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml +++ b/somnio_starter/__brick__/{{project_name.snakeCase()}}/packages/{{project_name.snakeCase()}}_ui/pubspec.yaml @@ -10,7 +10,6 @@ environment: dependencies: flutter: sdk: flutter - flutter_asset_generator: ^1.2.0 flutter_svg: ^2.0.10+1 dev_dependencies: diff --git a/todo/.github/PULL_REQUEST_TEMPLATE.md b/todo/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 4e11e3e..0000000 --- a/todo/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,31 +0,0 @@ - - -## Description - - - -## Type of Change - - - -- [ ] ✨ New feature (non-breaking change which adds functionality) -- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) -- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) -- [ ] 🧹 Code refactor -- [ ] ✅ Build configuration change -- [ ] 📝 Documentation -- [ ] 🗑️ Chore - -## Quality agreement - - - -- [ ] 👀 Did you run the command: `flutter analyze packages lib test`? -- [ ] 👀 Did you run the command: `dart format --line-length 80 .`? -- [ ] 👀 No environment variables commited into source control (use `.env`) diff --git a/todo/.github/cspell.json b/todo/.github/cspell.json deleted file mode 100644 index 3286743..0000000 --- a/todo/.github/cspell.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "version": "0.2", - "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", - "dictionaries": [ - "vgv_allowed", - "vgv_forbidden" - ], - "dictionaryDefinitions": [ - { - "name": "vgv_allowed", - "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt", - "description": "Allowed VGV Spellings" - }, - { - "name": "vgv_forbidden", - "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt", - "description": "Forbidden VGV Spellings" - } - ], - "useGitignore": true, - "words": [ - "Contador", - "localizable", - "mostrado", - "página", - "Texto", - "appbar", - "gifs", - "Noto", - "Everytime" - ] -} \ No newline at end of file diff --git a/todo/.github/dependabot.yaml b/todo/.github/dependabot.yaml deleted file mode 100644 index ca17430..0000000 --- a/todo/.github/dependabot.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - target-branch: "develop" - commit-message: - prefix: "chore" - include: "scope" - - package-ecosystem: "pub" - directory: "/" - schedule: - interval: "daily" - target-branch: "develop" - commit-message: - prefix: "chore" - include: "scope" diff --git a/todo/.github/workflows/api_client.yaml b/todo/.github/workflows/api_client.yaml deleted file mode 100644 index 1c95e1c..0000000 --- a/todo/.github/workflows/api_client.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: api_client - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/api_client/**" - - ".github/workflows/api_client.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 - with: - dart_sdk: 3.4.4 - working_directory: packages/api_client - coverage_excludes: "**/*.g.dart" - min_coverage: 95 \ No newline at end of file diff --git a/todo/.github/workflows/auth_client.yaml b/todo/.github/workflows/auth_client.yaml deleted file mode 100644 index 62ec70b..0000000 --- a/todo/.github/workflows/auth_client.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: auth_client - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/auth_client/**" - - ".github/workflows/auth_client.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 - with: - dart_sdk: 3.4.4 - working_directory: packages/auth_client - coverage_excludes: "**/*.g.dart" - min_coverage: 80 \ No newline at end of file diff --git a/todo/.github/workflows/form_inputs.yaml b/todo/.github/workflows/form_inputs.yaml deleted file mode 100644 index cc01664..0000000 --- a/todo/.github/workflows/form_inputs.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: form_inputs - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/form_inputs/**" - - ".github/workflows/form_inputs.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 - with: - dart_sdk: 3.4.4 - working_directory: packages/form_inputs - coverage_excludes: "**/*.g.dart" - min_coverage: 95 \ No newline at end of file diff --git a/todo/.github/workflows/main.yaml b/todo/.github/workflows/main.yaml deleted file mode 100644 index e76adcd..0000000 --- a/todo/.github/workflows/main.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: todo - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - semantic-pull-request: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 - - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 - with: - flutter_channel: stable - flutter_version: 3.22.3 - min_coverage: 0 - - spell-check: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 - with: - includes: | - **/*.md - modified_files_only: false diff --git a/todo/.github/workflows/todo_ui.yaml b/todo/.github/workflows/todo_ui.yaml deleted file mode 100644 index 0997d51..0000000 --- a/todo/.github/workflows/todo_ui.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: todo_ui - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/todo_ui/**" - - ".github/workflows/todo_ui.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 - with: - coverage_excludes: "**/*.g.dart" - flutter_channel: stable - flutter_version: 3.22.3 - working_directory: packages/todo_ui - min_coverage: 80 - analyze_directories: lib gallery test - format_directories: lib gallery test diff --git a/todo/.github/workflows/token_provider.yaml b/todo/.github/workflows/token_provider.yaml deleted file mode 100644 index 097f76a..0000000 --- a/todo/.github/workflows/token_provider.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: token_provider - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/token_provider/**" - - ".github/workflows/token_provider.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 - with: - dart_sdk: 3.4.4 - working_directory: packages/token_provider - coverage_excludes: "**/*.g.dart" - min_coverage: 100 \ No newline at end of file diff --git a/todo/.github/workflows/user_repository.yaml b/todo/.github/workflows/user_repository.yaml deleted file mode 100644 index 740b6a4..0000000 --- a/todo/.github/workflows/user_repository.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: user_repository - -concurrency: - group: $-$ - cancel-in-progress: true - -on: - pull_request: - paths: - - "packages/user_repository/**" - - ".github/workflows/user_repository.yaml" - -jobs: - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 - with: - dart_sdk: 3.4.4 - working_directory: packages/user_repository - coverage_excludes: "**/*.g.dart" - min_coverage: 85 \ No newline at end of file diff --git a/todo/.gitignore b/todo/.gitignore deleted file mode 100644 index 187bf26..0000000 --- a/todo/.gitignore +++ /dev/null @@ -1,117 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/* - -# Visual Studio Code related -.classpath -.project -.settings/ -.vscode/* - -# packages file containing multi-root paths -.packages.generated - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ -flutter_*.png -linked_*.ds -unlinked.ds -unlinked_spec.ds -.fvm/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java -**/android/key.properties -**/android/.idea/ -**/android/app/debug -**/android/app/profile -**/android/app/release -*.jks - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/.last_build_id -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Coverage -coverage/ - -# Submodules -packages/**/pubspec.lock - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Exceptions to the above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -!/dev/ci/**/Gemfile.lock -!.vscode/extensions.json -!.vscode/launch.json -!.idea/codeStyles/ -!.idea/dictionaries/ -!.idea/runConfigurations/ - -# App key files -app-keys.dev.json -app-keys.stg.json -app-keys.prod.json diff --git a/todo/.idea/runConfigurations/development.xml b/todo/.idea/runConfigurations/development.xml deleted file mode 100644 index 07b02c6..0000000 --- a/todo/.idea/runConfigurations/development.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/.idea/runConfigurations/production.xml b/todo/.idea/runConfigurations/production.xml deleted file mode 100644 index 1c5c774..0000000 --- a/todo/.idea/runConfigurations/production.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/.idea/runConfigurations/staging.xml b/todo/.idea/runConfigurations/staging.xml deleted file mode 100644 index f979a68..0000000 --- a/todo/.idea/runConfigurations/staging.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/.metadata b/todo/.metadata deleted file mode 100644 index 59a5a1f..0000000 --- a/todo/.metadata +++ /dev/null @@ -1,33 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: macos - create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 - base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 - - platform: ios - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/todo/.vscode/extensions.json b/todo/.vscode/extensions.json deleted file mode 100644 index 5500bce..0000000 --- a/todo/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dart-code.dart-code", - "dart-code.flutter", - "felixangelov.bloc" - ] -} diff --git a/todo/.vscode/launch.json b/todo/.vscode/launch.json deleted file mode 100644 index 356cf37..0000000 --- a/todo/.vscode/launch.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch development", - "request": "launch", - "type": "dart", - "program": "lib/main/main_development.dart", - "args": [ - "--flavor", - "development", - "--target", - "lib/main/main_development.dart", - "--dart-define-from-file", - "app-keys.dev.json" - ] - }, - { - "name": "Launch staging", - "request": "launch", - "type": "dart", - "program": "lib/main/main_staging.dart", - "args": [ - "--flavor", - "staging", - "--target", - "lib/main/main_staging.dart", - "--dart-define-from-file", - "app-keys.stg.json" - ] - }, - { - "name": "Launch production", - "request": "launch", - "type": "dart", - "program": "lib/main/main_production.dart", - "args": [ - "--flavor", - "production", - "--target", - "lib/main/main_production.dart", - "--dart-define-from-file", - "app-keys.prod.json" - ] - }, - { - "name": "Launch web development", - "request": "launch", - "type": "dart", - "program": "lib/main/main_development.dart", - "args": [ - "--target", - "lib/main/main_development.dart", - "--dart-define-from-file", - "app-keys.dev.json" - ] - }, - { - "name": "Launch web staging", - "request": "launch", - "type": "dart", - "program": "lib/main/main_staging.dart", - "args": [ - "--target", - "lib/main_staging.dart", - "--dart-define-from-file", - "app-keys.stg.json" - ] - }, - { - "name": "Launch web production", - "request": "launch", - "type": "dart", - "program": "lib/main/main_production.dart", - "args": [ - "--target", - "lib/main_production.dart", - "--dart-define-from-file", - "app-keys.prod.json" - ] - }, - { - "name": "Launch gallery", - "request": "launch", - "type": "dart", - "program": "packages/todo_ui/gallery/lib/main.dart" - } - ] -} - - - diff --git a/todo/README.md b/todo/README.md deleted file mode 100644 index 2a993e8..0000000 --- a/todo/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# Todo - -![coverage][coverage_badge] -[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] -[![License: MIT][license_badge]][license_link] - -A Somnio App - ---- - -## Getting Started 🚀 - -This project contains 3 flavors: - -- development -- staging -- production - -To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands: - -```sh -# Development -$ flutter run --flavor development --target lib/main_development.dart --dart-define-from-file app-keys.dev.json - -# Staging -$ flutter run --flavor staging --target lib/main_staging.dart --dart-define-from-file app-keys.stg.json - -# Production -$ flutter run --flavor production --target lib/main_production.dart --dart-define-from-file app-keys.prod.json -``` - -_\*Todo works on iOS, Android, Web, and Windows._ - ---- -Use this example to create a file to store the environment variables: -```sh -# Keys for development flavor -$ cp app-keys.copy.json app-keys.dev.json -# Keys for staging flavor -$ cp app-keys.copy.json app-keys.stg.json -# Keys for production flavor -$ cp app-keys.copy.json app-keys.prod.json -``` - ---- - -## Running Tests 🧪 - -To run all unit and widget tests use the following command: - -```sh -$ flutter test --coverage --test-randomize-ordering-seed random -``` - -To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov). - -```sh -# Generate Coverage Report -$ genhtml coverage/lcov.info -o coverage/ - -# Open Coverage Report -$ open coverage/index.html -``` - ---- - -## Working with Translations 🌐 - -This project relies on [flutter_localizations][flutter_localizations_link] and follows the [official internationalization guide for Flutter][internationalization_link]. - -### Adding Strings - -1. To add a new localizable string, open the `app_en.arb` file at `lib/l10n/arb/app_en.arb`. - -```arb -{ - "@@locale": "en", - "counterAppBarTitle": "Counter", - "@counterAppBarTitle": { - "description": "Text shown in the AppBar of the Counter Page" - } -} -``` - -2. Then add a new key/value and description - -```arb -{ - "@@locale": "en", - "counterAppBarTitle": "Counter", - "@counterAppBarTitle": { - "description": "Text shown in the AppBar of the Counter Page" - }, - "helloWorld": "Hello World", - "@helloWorld": { - "description": "Hello World Text" - } -} -``` - -3. Use the new string - -```dart -import 'package:todo/l10n/l10n.dart'; - -@override -Widget build(BuildContext context) { - final l10n = context.l10n; - return Text(l10n.helloWorld); -} -``` - -### Adding Supported Locales - -Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale. - -```xml - ... - - CFBundleLocalizations - - en - es - - - ... -``` - -### Adding Translations - -1. For each supported locale, add a new ARB file in `lib/l10n/arb`. - -``` -├── l10n -│ ├── arb -│ │ ├── app_en.arb -│ │ └── app_es.arb -``` - -2. Add the translated strings to each `.arb` file: - -`app_en.arb` - -```arb -{ - "@@locale": "en", - "counterAppBarTitle": "Counter", - "@counterAppBarTitle": { - "description": "Text shown in the AppBar of the Counter Page" - } -} -``` - -`app_es.arb` - -```arb -{ - "@@locale": "es", - "counterAppBarTitle": "Contador", - "@counterAppBarTitle": { - "description": "Texto mostrado en la AppBar de la página del contador" - } -} -``` - -### Generating Translations - -To use the latest translations changes, you will need to generate them: - -1. Generate localizations for the current project: - -```sh -flutter gen-l10n --arb-dir="lib/l10n/arb" -``` - -Alternatively, run `flutter run` and code generation will take place automatically. - -[coverage_badge]: coverage_badge.svg -[flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html -[internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization -[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg -[license_link]: https://opensource.org/licenses/MIT -[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg -[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis diff --git a/todo/analysis_options.yaml b/todo/analysis_options.yaml deleted file mode 100644 index 5105b91..0000000 --- a/todo/analysis_options.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" - -linter: - rules: - public_member_api_docs: false diff --git a/todo/android/.gitignore b/todo/android/.gitignore deleted file mode 100644 index be3943c..0000000 --- a/todo/android/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks diff --git a/todo/android/app/build.gradle.kts b/todo/android/app/build.gradle.kts deleted file mode 100644 index 0202476..0000000 --- a/todo/android/app/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "com.example.bricks" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.bricks" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } - } -} - -flutter { - source = "../.." -} diff --git a/todo/android/app/src/debug/AndroidManifest.xml b/todo/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/todo/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/android/app/src/main/AndroidManifest.xml b/todo/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 29c379f..0000000 --- a/todo/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt b/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt deleted file mode 100644 index ac09d0e..0000000 --- a/todo/android/app/src/main/kotlin/com/example/bricks/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.bricks - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/todo/android/app/src/main/res/drawable-v21/launch_background.xml b/todo/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f..0000000 --- a/todo/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/android/app/src/main/res/drawable/launch_background.xml b/todo/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/todo/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/todo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/todo/android/app/src/main/res/values-night/styles.xml b/todo/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be..0000000 --- a/todo/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/todo/android/app/src/main/res/values/styles.xml b/todo/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88..0000000 --- a/todo/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/todo/android/app/src/profile/AndroidManifest.xml b/todo/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/todo/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/android/build.gradle.kts b/todo/android/build.gradle.kts deleted file mode 100644 index dbee657..0000000 --- a/todo/android/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = - rootProject.layout.buildDirectory - .dir("../../build") - .get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/todo/android/gradle.properties b/todo/android/gradle.properties deleted file mode 100644 index fbee1d8..0000000 --- a/todo/android/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true diff --git a/todo/android/gradle/wrapper/gradle-wrapper.properties b/todo/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e4ef43f..0000000 --- a/todo/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/todo/android/gradlew b/todo/android/gradlew deleted file mode 100644 index 9d82f78..0000000 --- a/todo/android/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/todo/android/gradlew.bat b/todo/android/gradlew.bat deleted file mode 100644 index aec9973..0000000 --- a/todo/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/todo/android/settings.gradle.kts b/todo/android/settings.gradle.kts deleted file mode 100644 index ca7fe06..0000000 --- a/todo/android/settings.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - val flutterSdkPath = - run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false -} - -include(":app") diff --git a/todo/app-keys.copy.json b/todo/app-keys.copy.json deleted file mode 100644 index 12a8fc2..0000000 --- a/todo/app-keys.copy.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "KEY_ANDROID": "", - "KEY_IOS": "" -} \ No newline at end of file diff --git a/todo/coverage_badge.svg b/todo/coverage_badge.svg deleted file mode 100644 index 88bfadf..0000000 --- a/todo/coverage_badge.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - coverage - coverage - 100% - 100% - - \ No newline at end of file diff --git a/todo/ios/.gitignore b/todo/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/todo/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/todo/ios/Flutter/AppFrameworkInfo.plist b/todo/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 1dc6cf7..0000000 --- a/todo/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 13.0 - - diff --git a/todo/ios/Flutter/Debug.xcconfig b/todo/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/todo/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/todo/ios/Flutter/Release.xcconfig b/todo/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/todo/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/todo/ios/Podfile b/todo/ios/Podfile deleted file mode 100644 index 620e46e..0000000 --- a/todo/ios/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '13.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/todo/ios/Runner.xcodeproj/project.pbxproj b/todo/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 42ac687..0000000 --- a/todo/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,619 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 332S4VAVCD; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 332S4VAVCD; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 332S4VAVCD; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.bricks; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/todo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/todo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index e3773d4..0000000 --- a/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner.xcworkspace/contents.xcworkspacedata b/todo/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a1..0000000 --- a/todo/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/todo/ios/Runner/AppDelegate.swift b/todo/ios/Runner/AppDelegate.swift deleted file mode 100644 index 6266644..0000000 --- a/todo/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner/Base.lproj/Main.storyboard b/todo/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/todo/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/ios/Runner/Info.plist b/todo/ios/Runner/Info.plist deleted file mode 100644 index fa50ea4..0000000 --- a/todo/ios/Runner/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Bricks - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - bricks - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/todo/ios/Runner/Runner-Bridging-Header.h b/todo/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/todo/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/todo/ios/RunnerTests/RunnerTests.swift b/todo/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b..0000000 --- a/todo/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/todo/l10n.yaml b/todo/l10n.yaml deleted file mode 100644 index 6f72a55..0000000 --- a/todo/l10n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -arb-dir: lib/l10n/arb -template-arb-file: app_en.arb -output-localization-file: app_localizations.dart -nullable-getter: false diff --git a/todo/lib/app/app.dart b/todo/lib/app/app.dart deleted file mode 100644 index 5811d3c..0000000 --- a/todo/lib/app/app.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'bloc/app_bloc.dart'; -export 'extensions/extensions.dart'; -export 'router/app_router.dart'; -export 'view/app.dart'; diff --git a/todo/lib/app/bloc/app_bloc.dart b/todo/lib/app/bloc/app_bloc.dart deleted file mode 100644 index a485704..0000000 --- a/todo/lib/app/bloc/app_bloc.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'dart:async'; - -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:user_repository/user_repository.dart'; - -part 'app_event.dart'; -part 'app_state.dart'; - -class AppBloc extends Bloc { - AppBloc({ - required User? user, - required UserRepository userRepository, - required AppConfigRepository appConfigRepository, - }) : _userRepository = userRepository, - super( - user == null - ? const AppState.unauthenticated( - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ) - : AppState.authenticated( - user: user, - forceUpgrade: const ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ) { - on(_onAppUserChanged); - on( - _onAppDownForMaintenanceStatusChanged, - ); - on(_onAppLogoutRequested); - on(_onAppForceUpgradeStatusChanged); - _userSubscription = _userRepository.user.listen(_onUser); - _forceUpgradeSubscription = appConfigRepository - .isForceUpgradeRequired() - .listen(_forceUpgradeStatusChanged); - _isDownForMaintenanceSubscription = appConfigRepository - .isDownForMaintenance() - .listen(_downForMaintenanceStatusChanged); - } - - final UserRepository _userRepository; - late final StreamSubscription _userSubscription; - late StreamSubscription _forceUpgradeSubscription; - late StreamSubscription _isDownForMaintenanceSubscription; - - void _onUser(User? user) { - add(AppUserChanged(user: user)); - } - - void _downForMaintenanceStatusChanged(bool isDownForMaintenance) { - add( - AppDownForMaintenanceStatusChanged( - isDownForMaintenance: isDownForMaintenance, - ), - ); - } - - void _forceUpgradeStatusChanged(ForceUpgrade forceUpgrade) { - add(AppForceUpgradeStatusChanged(forceUpgrade)); - } - - Future _onAppUserChanged( - AppUserChanged event, - Emitter emit, - ) async { - final user = event.user; - if (user == null) { - emit( - AppState.unauthenticated( - forceUpgrade: state.forceUpgrade, - isDownForMaintenance: state.isDownForMaintenance, - ), - ); - } else { - emit( - AppState.authenticated( - user: user, - forceUpgrade: state.forceUpgrade, - isDownForMaintenance: state.isDownForMaintenance, - ), - ); - } - } - - void _onAppDownForMaintenanceStatusChanged( - AppDownForMaintenanceStatusChanged event, - Emitter emit, - ) { - final isDownForMaintenance = event.isDownForMaintenance; - if (isDownForMaintenance) { - emit( - AppState.downForMaintenance( - user: state.user, - forceUpgrade: state.forceUpgrade, - isDownForMaintenance: isDownForMaintenance, - ), - ); - } - } - - Future _onAppForceUpgradeStatusChanged( - AppForceUpgradeStatusChanged event, - Emitter emit, - ) async { - final forceUpgrade = event.forceUpgrade; - if (forceUpgrade.isUpgradeRequired) { - emit( - state.copyWith( - status: AppStatus.forceUpgrade, - forceUpgrade: forceUpgrade, - ), - ); - } - } - - Future _onAppLogoutRequested( - AppLogoutRequested event, - Emitter emit, - ) async { - if (state.user == null) return; - await _userRepository.signOut(); - emit( - state.copyWith(status: AppStatus.unauthenticated), - ); - } - - @override - Future close() { - unawaited(_userSubscription.cancel()); - _forceUpgradeSubscription.cancel(); - _isDownForMaintenanceSubscription.cancel(); - return super.close(); - } -} diff --git a/todo/lib/app/bloc/app_event.dart b/todo/lib/app/bloc/app_event.dart deleted file mode 100644 index 794b08d..0000000 --- a/todo/lib/app/bloc/app_event.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of 'app_bloc.dart'; - -abstract class AppEvent extends Equatable { - const AppEvent(); -} - -class AppUserChanged extends AppEvent { - @visibleForTesting - const AppUserChanged({required this.user}); - - final User? user; - - @override - List get props => [user]; -} - -class AppDownForMaintenanceStatusChanged extends AppEvent { - @visibleForTesting - const AppDownForMaintenanceStatusChanged({ - required this.isDownForMaintenance, - }); - - final bool isDownForMaintenance; - - @override - List get props => [isDownForMaintenance]; -} - -class AppForceUpgradeStatusChanged extends AppEvent { - @visibleForTesting - const AppForceUpgradeStatusChanged(this.forceUpgrade); - - final ForceUpgrade forceUpgrade; - - @override - List get props => [forceUpgrade]; -} - -class AppLogoutRequested extends AppEvent { - const AppLogoutRequested(); - - @override - List get props => []; -} diff --git a/todo/lib/app/bloc/app_state.dart b/todo/lib/app/bloc/app_state.dart deleted file mode 100644 index e58bf80..0000000 --- a/todo/lib/app/bloc/app_state.dart +++ /dev/null @@ -1,74 +0,0 @@ -part of 'app_bloc.dart'; - -enum AppStatus { - downForMaintenance, - authenticated, - unauthenticated, - forceUpgrade; -} - -class AppState extends Equatable { - const AppState({ - required this.user, - required this.status, - this.forceUpgrade = const ForceUpgrade(isUpgradeRequired: false), - this.isDownForMaintenance = false, - }); - - const AppState.authenticated({ - required User user, - required ForceUpgrade? forceUpgrade, - required bool? isDownForMaintenance, - }) : this( - user: user, - status: AppStatus.authenticated, - forceUpgrade: forceUpgrade, - isDownForMaintenance: isDownForMaintenance, - ); - - const AppState.unauthenticated({ - required ForceUpgrade? forceUpgrade, - required bool? isDownForMaintenance, - }) : this( - user: null, - status: AppStatus.unauthenticated, - forceUpgrade: forceUpgrade, - isDownForMaintenance: isDownForMaintenance, - ); - - const AppState.downForMaintenance({ - required ForceUpgrade? forceUpgrade, - required bool isDownForMaintenance, - User? user, - }) : this( - user: user, - status: AppStatus.downForMaintenance, - forceUpgrade: forceUpgrade, - isDownForMaintenance: isDownForMaintenance, - ); - - final User? user; - final AppStatus status; - final ForceUpgrade? forceUpgrade; - final bool? isDownForMaintenance; - - @override - List get props => [ - user, - status, - ]; - - AppState copyWith({ - User? user, - AppStatus? status, - ForceUpgrade? forceUpgrade, - bool? isDownForMaintenance, - }) { - return AppState( - user: user ?? this.user, - status: status ?? this.status, - forceUpgrade: forceUpgrade ?? this.forceUpgrade, - isDownForMaintenance: isDownForMaintenance ?? this.isDownForMaintenance, - ); - } -} diff --git a/todo/lib/app/constants/constants.dart b/todo/lib/app/constants/constants.dart deleted file mode 100644 index 246ee50..0000000 --- a/todo/lib/app/constants/constants.dart +++ /dev/null @@ -1 +0,0 @@ -export 'navigation_keys.dart'; diff --git a/todo/lib/app/constants/navigation_keys.dart b/todo/lib/app/constants/navigation_keys.dart deleted file mode 100644 index 94fd2eb..0000000 --- a/todo/lib/app/constants/navigation_keys.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:flutter/widgets.dart'; - -final rootNavigatorKey = GlobalKey(); diff --git a/todo/lib/app/extensions/build_context.dart b/todo/lib/app/extensions/build_context.dart deleted file mode 100644 index 7995834..0000000 --- a/todo/lib/app/extensions/build_context.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -extension BuildContextX on BuildContext { - User? get user => read().state.user; -} diff --git a/todo/lib/app/extensions/extensions.dart b/todo/lib/app/extensions/extensions.dart deleted file mode 100644 index b39f3ac..0000000 --- a/todo/lib/app/extensions/extensions.dart +++ /dev/null @@ -1 +0,0 @@ -export 'build_context.dart'; diff --git a/todo/lib/app/router/app_router.dart b/todo/lib/app/router/app_router.dart deleted file mode 100644 index 64d004e..0000000 --- a/todo/lib/app/router/app_router.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:go_router/go_router.dart'; -import 'package:todo/app/constants/navigation_keys.dart'; -import 'package:todo/app/router/router.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; -import 'package:todo/authentication/login/login.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; -import 'package:todo/force_upgrade/force_upgrade.dart'; -import 'package:todo/home/home.dart'; -import 'package:todo/settings/settings.dart'; - -export 'auth_stream_scope.dart'; - -class AppRouter { - /// Only routes that are accessible to unauthenticated users - static const onlyUnauthenticatedUserRoutes = [ - SignUpPage.path, - LoginPage.path, - ForgotPasswordPage.path, - ]; - - /// Only routes that are accessible for authenticated users - static const onlyAuthenticatedUserRoutes = [ - HomePage.path, - SettingsPage.path, - ]; - - static GoRouter router() { - return GoRouter( - navigatorKey: rootNavigatorKey, - initialLocation: LoginPage.path, - redirect: (context, state) { - final path = state.uri.path; - final isAuthenticated = AppStatusStreamScope.of(context).isSignedIn; - final isUnauthenticated = AppStatusStreamScope.of(context).isSignedOut; - final isDownForMaintenance = - AppStatusStreamScope.of(context).isDownForMaintenance; - final forceUpgrade = AppStatusStreamScope.of(context).forceUpgrade; - if (isDownForMaintenance) { - return DownForMaintenancePage.path; - } - if (forceUpgrade) { - return ForceUpgradePage.path; - } - if (onlyUnauthenticatedUserRoutes.contains(path) && isAuthenticated) { - return HomePage.path; - } - if (onlyAuthenticatedUserRoutes.contains(path) && isUnauthenticated) { - return LoginPage.path; - } - return null; - }, - routes: [ - NavigationBarRouter(), - GoRoute( - path: SignUpPage.path, - pageBuilder: (context, state) { - return const SignUpPage(); - }, - ), - GoRoute( - path: LoginPage.path, - pageBuilder: (context, state) { - return const LoginPage(); - }, - ), - GoRoute( - path: DeleteAccountPage.path, - pageBuilder: (context, state) { - return const DeleteAccountPage(); - }, - ), - GoRoute( - path: ForgotPasswordPage.path, - pageBuilder: (context, state) { - return ForgotPasswordPage(); - }, - ), - GoRoute( - path: DownForMaintenancePage.path, - pageBuilder: (context, state) { - return const DownForMaintenancePage(); - }, - ), - GoRoute( - path: ForceUpgradePage.path, - pageBuilder: (context, state) { - return const ForceUpgradePage(); - }, - ), - GoRoute( - path: SettingsPage.path, - pageBuilder: (context, state) { - return const SettingsPage(); - }, - ), - ], - ); - } -} diff --git a/todo/lib/app/router/auth_stream_scope.dart b/todo/lib/app/router/auth_stream_scope.dart deleted file mode 100644 index 311f34c..0000000 --- a/todo/lib/app/router/auth_stream_scope.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:todo/app/app.dart'; - -class AppStatusStreamScope extends InheritedNotifier { - AppStatusStreamScope({ - required AppBloc appBloc, - required super.child, - super.key, - }) : super(notifier: AppStatusStream(appBloc, appBloc.stream)); - - static AppStatusStream of(BuildContext ctx) => - ctx.dependOnInheritedWidgetOfExactType()!.notifier!; -} - -class AppStatusStream extends ChangeNotifier { - AppStatusStream( - AppBloc bloc, - Stream stream, - ) : _bloc = bloc { - _subscription = stream.asBroadcastStream().listen((_) => notifyListeners()); - } - - final AppBloc _bloc; - late final StreamSubscription _subscription; - - bool get isSignedIn => _bloc.state.status == AppStatus.authenticated; - bool get isSignedOut => _bloc.state.status == AppStatus.unauthenticated; - bool get isDownForMaintenance => - _bloc.state.status == AppStatus.downForMaintenance; - bool get forceUpgrade => _bloc.state.status == AppStatus.forceUpgrade; - - @override - void dispose() { - _subscription.cancel(); - super.dispose(); - } -} diff --git a/todo/lib/app/router/navigation_bar_router.dart b/todo/lib/app/router/navigation_bar_router.dart deleted file mode 100644 index 13e4d32..0000000 --- a/todo/lib/app/router/navigation_bar_router.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:go_router/go_router.dart'; -import 'package:todo/home/home.dart'; -import 'package:todo/settings/view/view.dart'; - -class NavigationBarRouter extends ShellRoute { - NavigationBarRouter() - : super( - pageBuilder: (context, state, child) { - int? index; - if (state.fullPath == HomePage.path) { - index = 0; - } else if (state.fullPath == SettingsPage.path) { - index = 1; - } - - return MainScreenPageBuilder.route(child, index); - }, - routes: [ - GoRoute( - path: HomePage.path, - pageBuilder: (context, state) { - return const HomePage(); - }, - ), - GoRoute( - path: SettingsPage.path, - pageBuilder: (context, state) { - return const SettingsPage(); - }, - ), - ], - ); -} diff --git a/todo/lib/app/router/router.dart b/todo/lib/app/router/router.dart deleted file mode 100644 index c290ccd..0000000 --- a/todo/lib/app/router/router.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'app_router.dart'; -export 'auth_stream_scope.dart'; -export 'navigation_bar_router.dart'; diff --git a/todo/lib/app/view/app.dart b/todo/lib/app/view/app.dart deleted file mode 100644 index 3bfac7e..0000000 --- a/todo/lib/app/view/app.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/app/app.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo_ui/todo_ui.dart'; -import 'package:user_repository/user_repository.dart'; - -class App extends StatelessWidget { - App({ - required UserRepository userRepository, - required User? user, - required AppConfigRepository appConfigRepository, - super.key, - }) : _user = user, - _appConfigRepository = appConfigRepository, - _routerConfig = AppRouter.router(), - _userRepository = userRepository; - - final User? _user; - final UserRepository _userRepository; - final GoRouter _routerConfig; - final AppConfigRepository _appConfigRepository; - - @override - Widget build(BuildContext context) { - return MultiRepositoryProvider( - providers: [ - RepositoryProvider.value(value: _userRepository), - RepositoryProvider.value(value: _appConfigRepository), - ], - child: BlocProvider( - create: (context) => AppBloc( - userRepository: _userRepository, - user: _user, - appConfigRepository: _appConfigRepository, - ), - child: AppView( - routerConfig: _routerConfig, - ), - ), - ); - } -} - -class AppView extends StatelessWidget { - const AppView({ - required GoRouter routerConfig, - super.key, - }) : _routerConfig = routerConfig; - - final GoRouter _routerConfig; - - @override - Widget build(BuildContext context) { - return AppStatusStreamScope( - appBloc: context.read(), - child: MaterialApp.router( - theme: UITheme().lightTheme, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - routerConfig: _routerConfig, - title: 'todo', - ), - ); - } -} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart b/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart deleted file mode 100644 index 6bc3635..0000000 --- a/todo/lib/authentication/delete_account/bloc/delete_account_bloc.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:user_repository/user_repository.dart'; - -part 'delete_account_event.dart'; -part 'delete_account_state.dart'; - -class DeleteAccountBloc extends Bloc { - DeleteAccountBloc({ - required UserRepository userRepository, - }) : _userRepository = userRepository, - super(const DeleteAccountState.initial()) { - on(_onDeleteAccountRequested); - on(_onEmailChanged); - on(_onPasswordChanged); - on(_onDeleteAccountSubmitted); - on(_onDeleteAccountResetted); - } - - // ignore: unused_field - final UserRepository _userRepository; - - FutureOr _onDeleteAccountRequested( - DeleteAccountRequested event, - Emitter emit, - ) async { - try { - emit(state.copyWith(status: DeleteAccountStatus.loading)); - await _userRepository.deleteAccount(); - emit(state.copyWith(status: DeleteAccountStatus.reauthenticated)); - } on UserReAuthenticateFailure { - emit( - state.copyWith( - status: DeleteAccountStatus.reauthFailure, - ), - ); - } on Exception { - emit( - state.copyWith( - status: DeleteAccountStatus.failure, - ), - ); - } - } - - FutureOr _onEmailChanged( - DeleteAccountEmailChanged event, - Emitter emit, - ) { - emit(state.copyWith(email: Email.dirty(event.email))); - } - - FutureOr _onPasswordChanged( - DeleteAccountPasswordChanged event, - Emitter emit, - ) { - emit(state.copyWith(password: Password.dirty(event.password))); - } - - FutureOr _onDeleteAccountSubmitted( - DeleteAccountSubmitted event, - Emitter emit, - ) async { - try { - emit(state.copyWith(status: DeleteAccountStatus.loading)); - await _userRepository.deleteAccount(); - emit(state.copyWith(status: DeleteAccountStatus.success)); - } on UserDeleteAccountFailure { - emit(state.copyWith(status: DeleteAccountStatus.deleteAccountFailure)); - } on Exception { - emit(state.copyWith(status: DeleteAccountStatus.failure)); - } - } - - FutureOr _onDeleteAccountResetted( - DeleteAccountResetted event, - Emitter emit, - ) { - emit(state.copyWith(status: DeleteAccountStatus.initial)); - } -} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_event.dart b/todo/lib/authentication/delete_account/bloc/delete_account_event.dart deleted file mode 100644 index dcd1e1b..0000000 --- a/todo/lib/authentication/delete_account/bloc/delete_account_event.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of 'delete_account_bloc.dart'; - -abstract class DeleteAccountEvent extends Equatable { - const DeleteAccountEvent(); -} - -class DeleteAccountRequested extends DeleteAccountEvent { - const DeleteAccountRequested(); - - @override - List get props => []; -} - -class DeleteAccountSubmitted extends DeleteAccountEvent { - const DeleteAccountSubmitted(); - - @override - List get props => []; -} - -class DeleteAccountEmailChanged extends DeleteAccountEvent { - const DeleteAccountEmailChanged(this.email); - - final String email; - - @override - List get props => [email]; -} - -class DeleteAccountPasswordChanged extends DeleteAccountEvent { - const DeleteAccountPasswordChanged(this.password); - - final String password; - - @override - List get props => [password]; -} - -class DeleteAccountResetted extends DeleteAccountEvent { - const DeleteAccountResetted(); - - @override - List get props => []; -} diff --git a/todo/lib/authentication/delete_account/bloc/delete_account_state.dart b/todo/lib/authentication/delete_account/bloc/delete_account_state.dart deleted file mode 100644 index 02011a7..0000000 --- a/todo/lib/authentication/delete_account/bloc/delete_account_state.dart +++ /dev/null @@ -1,51 +0,0 @@ -part of 'delete_account_bloc.dart'; - -enum DeleteAccountStatus { - initial, - loading, - success, - reauthenticated, - reauthFailure, - deleteAccountFailure, - failure; -} - -class DeleteAccountState extends Equatable { - const DeleteAccountState({ - required this.status, - required this.email, - required this.password, - }); - - const DeleteAccountState.initial() - : this( - status: DeleteAccountStatus.initial, - email: const Email.pure(), - password: const Password.pure(), - ); - - final DeleteAccountStatus status; - final Email email; - final Password password; - - DeleteAccountState copyWith({ - DeleteAccountStatus? status, - Email? email, - Password? password, - }) { - return DeleteAccountState( - status: status ?? this.status, - email: email ?? this.email, - password: password ?? this.password, - ); - } - - bool get valid => Formz.validate([email, password]); - - @override - List get props => [ - status, - email, - password, - ]; -} diff --git a/todo/lib/authentication/delete_account/delete_account.dart b/todo/lib/authentication/delete_account/delete_account.dart deleted file mode 100644 index 690bb3d..0000000 --- a/todo/lib/authentication/delete_account/delete_account.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/delete_account_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/authentication/delete_account/view/delete_account_page.dart b/todo/lib/authentication/delete_account/view/delete_account_page.dart deleted file mode 100644 index d07acd0..0000000 --- a/todo/lib/authentication/delete_account/view/delete_account_page.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:user_repository/user_repository.dart'; - -class DeleteAccountPage extends Page { - const DeleteAccountPage(); - - static const path = '/delete-account'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - fullscreenDialog: true, - settings: this, - builder: (ctx) { - return BlocProvider( - create: (_) => DeleteAccountBloc( - userRepository: ctx.read(), - ), - child: const DeleteAccountView(), - ); - }, - ); - } -} diff --git a/todo/lib/authentication/delete_account/view/delete_account_view.dart b/todo/lib/authentication/delete_account/view/delete_account_view.dart deleted file mode 100644 index fdc71c3..0000000 --- a/todo/lib/authentication/delete_account/view/delete_account_view.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class DeleteAccountView extends StatelessWidget { - const DeleteAccountView({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return Scaffold( - appBar: AppBar( - title: Text(l10n.deleteAccount), - leading: const BackButton(), - ), - body: const SafeArea( - child: Padding( - padding: EdgeInsets.all(UISpacing.xlg), - child: DeleteAccountForm(), - ), - ), - ); - } -} - -class DeleteAccountForm extends StatelessWidget { - const DeleteAccountForm({super.key}); - - @override - Widget build(BuildContext context) { - return const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _EmailField(), - SizedBox(height: UISpacing.md), - _PasswordField(), - SizedBox(height: UISpacing.md), - _DeleteAccountButton(), - ], - ); - } -} - -class _PasswordField extends StatefulWidget { - const _PasswordField(); - - @override - State<_PasswordField> createState() => _PasswordFieldState(); -} - -class _PasswordFieldState extends State<_PasswordField> { - @override - Widget build(BuildContext context) { - final password = context.select( - (DeleteAccountBloc bloc) => bloc.state.password, - ); - final initialValue = password.value; - return UITextField.passwordTextField( - key: const Key('DeleteAccountPassword'), - initialValue: initialValue, - onChanged: (password) { - context.read().add( - DeleteAccountPasswordChanged(password), - ); - }, - ); - } -} - -class _EmailField extends StatelessWidget { - const _EmailField(); - - @override - Widget build(BuildContext context) { - final email = context.read().state.email; - final initialValue = email.value; - return UITextField.emailTextField( - key: const Key('DeleteAccountEmail'), - initialValue: initialValue, - onChanged: (email) { - context.read().add( - DeleteAccountEmailChanged(email), - ); - }, - ); - } -} - -class _DeleteAccountButton extends StatelessWidget { - const _DeleteAccountButton(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final isValid = - context.select((DeleteAccountBloc bloc) => bloc.state.valid); - - return UIOutlinedButton.primary( - text: l10n.deleteAccount, - onPressed: isValid - ? () { - context.read().add( - const DeleteAccountSubmitted(), - ); - } - : null, - ); - } -} diff --git a/todo/lib/authentication/delete_account/view/view.dart b/todo/lib/authentication/delete_account/view/view.dart deleted file mode 100644 index 304ae6e..0000000 --- a/todo/lib/authentication/delete_account/view/view.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'delete_account_page.dart'; -export 'delete_account_view.dart'; diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart deleted file mode 100644 index edc6fcd..0000000 --- a/todo/lib/authentication/forgot_password/bloc/forgot_password_bloc.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:async'; - -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:user_repository/user_repository.dart'; - -part 'forgot_password_event.dart'; -part 'forgot_password_state.dart'; - -class ForgotPasswordBloc - extends Bloc { - ForgotPasswordBloc({required UserRepository userRepository}) - : _userRepository = userRepository, - super(const ForgotPasswordState.initial()) { - on(_onEmailChanged); - on(_onSubmitted); - } - final UserRepository _userRepository; - - FutureOr _onEmailChanged( - ForgotPasswordEmailChanged event, - Emitter emit, - ) { - emit(state.copyWith(email: Email.dirty(event.email))); - } - - FutureOr _onSubmitted( - ForgotPasswordSubmitted event, - Emitter emit, - ) async { - try { - emit(state.copyWith(status: ForgotPasswordStatus.loading)); - await _userRepository.forgotPassword(email: state.email.value); - emit(state.copyWith(status: ForgotPasswordStatus.success)); - } on Exception { - emit(state.copyWith(status: ForgotPasswordStatus.error)); - } - } -} diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart deleted file mode 100644 index 7d8cda9..0000000 --- a/todo/lib/authentication/forgot_password/bloc/forgot_password_event.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of 'forgot_password_bloc.dart'; - -abstract class ForgotPasswordEvent extends Equatable { - const ForgotPasswordEvent(); -} - -class ForgotPasswordSubmitted extends ForgotPasswordEvent { - const ForgotPasswordSubmitted(); - - @override - List get props => []; -} - -class ForgotPasswordEmailChanged extends ForgotPasswordEvent { - const ForgotPasswordEmailChanged(this.email); - - final String email; - - @override - List get props => [email]; -} diff --git a/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart b/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart deleted file mode 100644 index 65c650b..0000000 --- a/todo/lib/authentication/forgot_password/bloc/forgot_password_state.dart +++ /dev/null @@ -1,49 +0,0 @@ -part of 'forgot_password_bloc.dart'; - -enum ForgotPasswordStatus { - initial, - loading, - loaded, - error, - success; - - bool get isInitial => this == ForgotPasswordStatus.initial; - bool get isLoading => this == ForgotPasswordStatus.loading; - bool get isLoaded => this == ForgotPasswordStatus.loaded; - bool get isSuccess => this == ForgotPasswordStatus.success; - bool get isError => this == ForgotPasswordStatus.error; -} - -class ForgotPasswordState extends Equatable { - const ForgotPasswordState({ - required this.status, - required this.email, - }); - - const ForgotPasswordState.initial() - : this( - status: ForgotPasswordStatus.initial, - email: const Email.pure(), - ); - - final ForgotPasswordStatus status; - final Email email; - - bool get valid => Formz.validate([email]); - - ForgotPasswordState copyWith({ - ForgotPasswordStatus? status, - Email? email, - }) { - return ForgotPasswordState( - status: status ?? this.status, - email: email ?? this.email, - ); - } - - @override - List get props => [ - status, - email, - ]; -} diff --git a/todo/lib/authentication/forgot_password/forgot_password.dart b/todo/lib/authentication/forgot_password/forgot_password.dart deleted file mode 100644 index 1f3ee54..0000000 --- a/todo/lib/authentication/forgot_password/forgot_password.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/forgot_password_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/authentication/forgot_password/view/forgot_password_page.dart b/todo/lib/authentication/forgot_password/view/forgot_password_page.dart deleted file mode 100644 index 65b26ae..0000000 --- a/todo/lib/authentication/forgot_password/view/forgot_password_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; - -class ForgotPasswordPage extends Page { - static const path = '/forgot_password'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - fullscreenDialog: true, - settings: this, - builder: (ctx) { - return BlocProvider( - create: (_) => ForgotPasswordBloc( - userRepository: context.read(), - ), - child: const ForgotPasswordView(), - ); - }, - ); - } -} diff --git a/todo/lib/authentication/forgot_password/view/forgot_password_view.dart b/todo/lib/authentication/forgot_password/view/forgot_password_view.dart deleted file mode 100644 index ce313a9..0000000 --- a/todo/lib/authentication/forgot_password/view/forgot_password_view.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; -import 'package:todo/l10n/l10n.dart'; - -class ForgotPasswordView extends StatelessWidget { - const ForgotPasswordView({super.key}); - - @override - Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - final l10n = context.l10n; - - if (state.status.isSuccess) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.resetSuccessful)), - ); - } - if (state.status.isError) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.unknownError)), - ); - } - }, - child: Scaffold( - appBar: AppBar( - leading: const BackButton(), - ), - body: const _ForgotPasswordForm(), - ), - ); - } -} - -class _ForgotPasswordForm extends StatelessWidget { - const _ForgotPasswordForm(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final status = - context.select((ForgotPasswordBloc bloc) => bloc.state.status); - - if (status == ForgotPasswordStatus.loading) { - return const Placeholder(); - } - return ListView( - padding: const EdgeInsets.all(24), - children: [ - Text(l10n.forgotPasswordDescription), - const SizedBox(height: 8), - const _EmailTextField(), - const SizedBox(height: 24), - const _ResetButton(), - const SizedBox(height: 24), - ], - ); - } -} - -class _ResetButton extends StatelessWidget { - const _ResetButton(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final valid = context.select((ForgotPasswordBloc bloc) => bloc.state.valid); - - return ElevatedButton( - onPressed: valid - ? () { - context - .read() - .add(const ForgotPasswordSubmitted()); - } - : null, - child: Text(l10n.reset), - ); - } -} - -class _EmailTextField extends StatelessWidget { - const _EmailTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final valid = context.select((ForgotPasswordBloc bloc) => bloc.state.valid); - - return TextField( - onChanged: (email) => context - .read() - .add(ForgotPasswordEmailChanged(email)), - decoration: InputDecoration( - hintText: l10n.email, - errorText: valid ? null : l10n.invalidEmail, - ), - keyboardType: TextInputType.emailAddress, - ); - } -} diff --git a/todo/lib/authentication/forgot_password/view/view.dart b/todo/lib/authentication/forgot_password/view/view.dart deleted file mode 100644 index dc573aa..0000000 --- a/todo/lib/authentication/forgot_password/view/view.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'forgot_password_page.dart'; -export 'forgot_password_view.dart'; diff --git a/todo/lib/authentication/login/bloc/login_bloc.dart b/todo/lib/authentication/login/bloc/login_bloc.dart deleted file mode 100644 index 85631af..0000000 --- a/todo/lib/authentication/login/bloc/login_bloc.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/widgets.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:user_repository/user_repository.dart'; - -part 'login_event.dart'; -part 'login_state.dart'; - -class LoginBloc extends Bloc { - LoginBloc({required UserRepository userRepository}) - : _userRepository = userRepository, - super(const LoginState.initial()) { - on(_onEmailChanged); - on(_onPasswordChanged); - on(_onPasswordVisibilityChanged); - on( - _onLoginWithEmailAndPasswordRequested, - ); - } - - final UserRepository _userRepository; - - FutureOr _onLoginWithEmailAndPasswordRequested( - LoginWithEmailAndPasswordRequested event, - Emitter emit, - ) async { - try { - emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); - await _userRepository.signIn( - email: state.email.value, - password: state.password.value, - ); - emit(state.copyWith(status: FormzSubmissionStatus.success)); - } on Exception { - emit(state.copyWith(status: FormzSubmissionStatus.failure)); - } - } - - FutureOr _onEmailChanged( - LoginEmailChanged event, - Emitter emit, - ) { - emit(state.copyWith(email: Email.dirty(event.email))); - } - - FutureOr _onPasswordChanged( - LoginPasswordChanged event, - Emitter emit, - ) { - emit(state.copyWith(password: Password.dirty(event.password))); - } - - FutureOr _onPasswordVisibilityChanged( - LoginPasswordVisibilityChanged event, - Emitter emit, - ) { - emit(state.copyWith(obscurePassword: event.obscure)); - } -} diff --git a/todo/lib/authentication/login/bloc/login_event.dart b/todo/lib/authentication/login/bloc/login_event.dart deleted file mode 100644 index 1ddc439..0000000 --- a/todo/lib/authentication/login/bloc/login_event.dart +++ /dev/null @@ -1,40 +0,0 @@ -part of 'login_bloc.dart'; - -@immutable -abstract class LoginEvent extends Equatable { - const LoginEvent(); -} - -class LoginEmailChanged extends LoginEvent { - const LoginEmailChanged(this.email); - - final String email; - - @override - List get props => [email]; -} - -class LoginPasswordChanged extends LoginEvent { - const LoginPasswordChanged(this.password); - - final String password; - - @override - List get props => [password]; -} - -class LoginWithEmailAndPasswordRequested extends LoginEvent { - const LoginWithEmailAndPasswordRequested(); - - @override - List get props => []; -} - -class LoginPasswordVisibilityChanged extends LoginEvent { - const LoginPasswordVisibilityChanged({required this.obscure}); - - final bool obscure; - - @override - List get props => [obscure]; -} diff --git a/todo/lib/authentication/login/bloc/login_state.dart b/todo/lib/authentication/login/bloc/login_state.dart deleted file mode 100644 index 2a968b4..0000000 --- a/todo/lib/authentication/login/bloc/login_state.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of 'login_bloc.dart'; - -@immutable -class LoginState extends Equatable { - const LoginState({ - required this.email, - required this.password, - required this.status, - required this.obscurePassword, - }); - - const LoginState.initial() - : this( - email: const Email.pure(), - password: const Password.pure(), - status: FormzSubmissionStatus.initial, - obscurePassword: true, - ); - - final Email email; - final Password password; - final FormzSubmissionStatus status; - final bool obscurePassword; - - bool get valid => Formz.validate([email, password]); - - @override - List get props => [email, password, status, obscurePassword]; - - LoginState copyWith({ - Email? email, - Password? password, - FormzSubmissionStatus? status, - bool? obscurePassword, - }) { - return LoginState( - email: email ?? this.email, - password: password ?? this.password, - status: status ?? this.status, - obscurePassword: obscurePassword ?? this.obscurePassword, - ); - } -} diff --git a/todo/lib/authentication/login/login.dart b/todo/lib/authentication/login/login.dart deleted file mode 100644 index 89d8fb1..0000000 --- a/todo/lib/authentication/login/login.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/login_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/authentication/login/view/login_page.dart b/todo/lib/authentication/login/view/login_page.dart deleted file mode 100644 index 094e446..0000000 --- a/todo/lib/authentication/login/view/login_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/login/login.dart'; - -class LoginPage extends Page { - const LoginPage({super.key}); - - static const path = '/login'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - fullscreenDialog: true, - settings: this, - builder: (context) { - return BlocProvider( - create: (_) => LoginBloc(userRepository: context.read()), - child: const LoginView(), - ); - }, - ); - } -} diff --git a/todo/lib/authentication/login/view/login_view.dart b/todo/lib/authentication/login/view/login_view.dart deleted file mode 100644 index ae45bd7..0000000 --- a/todo/lib/authentication/login/view/login_view.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; -import 'package:todo/authentication/login/login.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class LoginView extends StatelessWidget { - const LoginView({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return Scaffold( - appBar: UIAppBar(widgetTitle: Text(l10n.logIn)), - body: BlocListener( - listenWhen: (previous, current) => previous.status != current.status, - listener: (context, state) { - if (state.status.isSuccess) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.logInSuccessful)), - ); - } else if (state.status.isFailure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.unknownError)), - ); - } - }, - child: ListView( - padding: const EdgeInsets.all(24), - children: const [ - _EmailTextField(), - SizedBox(height: 24), - _PasswordTextField(), - SizedBox(height: 24), - _LoginButton(), - SizedBox(height: 24), - _ForgotPasswordButton(), - SizedBox(height: 24), - _SignUpButton(), - ], - ), - ), - ); - } -} - -class _ForgotPasswordButton extends StatelessWidget { - const _ForgotPasswordButton(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return TextButton( - key: const Key('LoginForgotPasswordButton'), - onPressed: () => context.push(ForgotPasswordPage.path), - child: Text(l10n.forgotPassword), - ); - } -} - -class _SignUpButton extends StatelessWidget { - const _SignUpButton(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - return TextButton( - key: const Key('LoginSignUpButton'), - onPressed: () => context.push(SignUpPage.path), - child: Text(l10n.signUp), - ); - } -} - -class _EmailTextField extends StatelessWidget { - const _EmailTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final valid = context.select((LoginBloc bloc) { - final email = bloc.state.email; - return email.isValid || email.isPure; - }); - - return UITextField.emailTextField( - key: const Key('LoginEmailTextField'), - onChanged: (value) => - context.read().add(LoginEmailChanged(value)), - hintText: l10n.email, - errorText: valid ? null : l10n.invalidEmail, - ); - } -} - -class _PasswordTextField extends StatelessWidget { - const _PasswordTextField(); - - @override - Widget build(BuildContext context) { - final obscurePassword = - context.select((LoginBloc bloc) => bloc.state.obscurePassword); - - return UITextField.passwordTextField( - key: const Key('LoginPasswordTextField'), - keyboardType: TextInputType.visiblePassword, - onChanged: (value) => - context.read().add(LoginPasswordChanged(value)), - suffix: IconButton( - icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), - onPressed: () { - context - .read() - .add(LoginPasswordVisibilityChanged(obscure: !obscurePassword)); - }, - ), - obscureText: obscurePassword, - ); - } -} - -class _LoginButton extends StatelessWidget { - const _LoginButton(); - - @override - Widget build(BuildContext context) { - final validToSubmit = context.select((LoginBloc bloc) => bloc.state.valid); - final l10n = context.l10n; - - return UIOutlinedButton.primary( - onPressed: validToSubmit - ? () { - context - .read() - .add(const LoginWithEmailAndPasswordRequested()); - } - : null, - text: l10n.logIn, - ); - } -} diff --git a/todo/lib/authentication/login/view/view.dart b/todo/lib/authentication/login/view/view.dart deleted file mode 100644 index 042cfbb..0000000 --- a/todo/lib/authentication/login/view/view.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'login_page.dart'; -export 'login_view.dart'; diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart b/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart deleted file mode 100644 index 40c824b..0000000 --- a/todo/lib/authentication/sign_up/bloc/sign_up_bloc.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/widgets.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:user_repository/user_repository.dart'; - -part 'sign_up_event.dart'; -part 'sign_up_state.dart'; - -class SignUpBloc extends Bloc { - SignUpBloc({required UserRepository userRepository}) - : _userRepository = userRepository, - super(const SignUpState.initial()) { - on(_onEmailChanged); - on(_onPasswordChanged); - on(_onConfirmationPasswordChanged); - on(_onPasswordVisibilityChanged); - on( - _onSignUpWithEmailAndPasswordRequested, - ); - on(_onNameChanged); - } - - final UserRepository _userRepository; - - FutureOr _onSignUpWithEmailAndPasswordRequested( - SignUpWithEmailAndPasswordRequested event, - Emitter emit, - ) async { - try { - emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); - await _userRepository.signUp( - email: state.email.value, - password: state.password.value, - name: state.name.value, - ); - emit(state.copyWith(status: FormzSubmissionStatus.success)); - } on Exception { - emit(state.copyWith(status: FormzSubmissionStatus.failure)); - } - } - - FutureOr _onEmailChanged( - SignUpEmailChanged event, - Emitter emit, - ) { - emit(state.copyWith(email: Email.dirty(event.email))); - } - - FutureOr _onPasswordChanged( - SignUpPasswordChanged event, - Emitter emit, - ) { - emit(state.copyWith(password: Password.dirty(event.password))); - } - - FutureOr _onPasswordVisibilityChanged( - SignUpPasswordVisibilityChanged event, - Emitter emit, - ) { - emit(state.copyWith(obscurePasswords: event.obscure)); - } - - FutureOr _onConfirmationPasswordChanged( - SignUpConfirmationPasswordChanged event, - Emitter emit, - ) { - emit(state.copyWith(confirmationPassword: event.password)); - } - - FutureOr _onNameChanged( - SignUpNameChanged event, - Emitter emit, - ) { - emit(state.copyWith(name: Name.dirty(event.name))); - } -} diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_event.dart b/todo/lib/authentication/sign_up/bloc/sign_up_event.dart deleted file mode 100644 index 85603ed..0000000 --- a/todo/lib/authentication/sign_up/bloc/sign_up_event.dart +++ /dev/null @@ -1,58 +0,0 @@ -part of 'sign_up_bloc.dart'; - -@immutable -abstract class SignUpEvent extends Equatable { - const SignUpEvent(); -} - -class SignUpEmailChanged extends SignUpEvent { - const SignUpEmailChanged(this.email); - - final String email; - - @override - List get props => [email]; -} - -class SignUpPasswordChanged extends SignUpEvent { - const SignUpPasswordChanged({required this.password}); - - final String password; - - @override - List get props => [password]; -} - -class SignUpConfirmationPasswordChanged extends SignUpEvent { - const SignUpConfirmationPasswordChanged({required this.password}); - - final String password; - - @override - List get props => [password]; -} - -class SignUpWithEmailAndPasswordRequested extends SignUpEvent { - const SignUpWithEmailAndPasswordRequested(); - - @override - List get props => []; -} - -class SignUpPasswordVisibilityChanged extends SignUpEvent { - const SignUpPasswordVisibilityChanged({required this.obscure}); - - final bool obscure; - - @override - List get props => [obscure]; -} - -class SignUpNameChanged extends SignUpEvent { - const SignUpNameChanged(this.name); - - final String name; - - @override - List get props => [name]; -} diff --git a/todo/lib/authentication/sign_up/bloc/sign_up_state.dart b/todo/lib/authentication/sign_up/bloc/sign_up_state.dart deleted file mode 100644 index e553dad..0000000 --- a/todo/lib/authentication/sign_up/bloc/sign_up_state.dart +++ /dev/null @@ -1,78 +0,0 @@ -part of 'sign_up_bloc.dart'; - -class SignUpState extends Equatable { - const SignUpState({ - required this.status, - required this.email, - required this.password, - required this.confirmationPassword, - required this.obscurePasswords, - required this.name, - }); - - const SignUpState.initial() - : this( - status: FormzSubmissionStatus.initial, - email: const Email.pure(), - password: const Password.pure(), - name: const Name.pure(), - confirmationPassword: '', - obscurePasswords: true, - ); - - final FormzSubmissionStatus status; - final Email email; - final Password password; - final Name name; - final String confirmationPassword; - final bool obscurePasswords; - - bool get valid { - return Formz.validate([email, password, name]) && - password.value == confirmationPassword; - } - - bool get emailIsValid { - return email.isPure || email.isValid; - } - - bool get passwordIsValid { - return password.isPure || password.isValid; - } - - bool get passwordsMatch { - return password.isPure || password.value == confirmationPassword; - } - - bool get nameIsValid { - return name.isPure || name.isValid; - } - - SignUpState copyWith({ - FormzSubmissionStatus? status, - Email? email, - Password? password, - String? confirmationPassword, - bool? obscurePasswords, - Name? name, - }) { - return SignUpState( - status: status ?? this.status, - confirmationPassword: confirmationPassword ?? this.confirmationPassword, - email: email ?? this.email, - obscurePasswords: obscurePasswords ?? this.obscurePasswords, - password: password ?? this.password, - name: name ?? this.name, - ); - } - - @override - List get props => [ - status, - email, - password, - confirmationPassword, - obscurePasswords, - name, - ]; -} diff --git a/todo/lib/authentication/sign_up/sign_up.dart b/todo/lib/authentication/sign_up/sign_up.dart deleted file mode 100644 index f6779d0..0000000 --- a/todo/lib/authentication/sign_up/sign_up.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/sign_up_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/authentication/sign_up/view/sign_up_page.dart b/todo/lib/authentication/sign_up/view/sign_up_page.dart deleted file mode 100644 index 4de33ad..0000000 --- a/todo/lib/authentication/sign_up/view/sign_up_page.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; - -class SignUpPage extends Page { - const SignUpPage(); - - static const path = '/sign-up'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - settings: this, - builder: (context) { - return BlocProvider( - create: (_) => SignUpBloc(userRepository: context.read()), - child: const SignUpView(), - ); - }, - ); - } -} diff --git a/todo/lib/authentication/sign_up/view/sign_up_view.dart b/todo/lib/authentication/sign_up/view/sign_up_view.dart deleted file mode 100644 index ce12f0a..0000000 --- a/todo/lib/authentication/sign_up/view/sign_up_view.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/authentication/sign_up/bloc/sign_up_bloc.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class SignUpView extends StatelessWidget { - const SignUpView({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return BlocListener( - listenWhen: (previous, current) => previous.status != current.status, - listener: (context, state) { - if (state.status.isSuccess) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.signUpSuccessful)), - ); - - if (context.canPop()) { - context.pop(); - } - } else if (state.status.isFailure) { - ScaffoldMessenger.of(context) - ..hideCurrentSnackBar() - ..showSnackBar( - SnackBar(content: Text(l10n.unknownError)), - ); - } - }, - child: Scaffold( - appBar: AppBar(title: Text(l10n.signUp)), - body: ListView( - padding: const EdgeInsets.all(UISpacing.xlg), - children: const [ - _NameTextField(), - SizedBox(height: UISpacing.xlg), - _EmailTextField(), - SizedBox(height: UISpacing.xlg), - _PasswordTextField(), - SizedBox(height: UISpacing.xlg), - _ConfirmationPasswordTextField(), - SizedBox(height: UISpacing.xlg), - _SignUpButton(), - ], - ), - ), - ); - } -} - -class _NameTextField extends StatelessWidget { - const _NameTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final valid = context.select((SignUpBloc bloc) => bloc.state.nameIsValid); - - return TextField( - key: const Key('SignUpNameTextField'), - onChanged: (name) => - context.read().add(SignUpNameChanged(name)), - decoration: InputDecoration( - hintText: l10n.signUpName, - errorText: valid ? null : l10n.signUpNameInvalid, - ), - keyboardType: TextInputType.text, - ); - } -} - -class _EmailTextField extends StatelessWidget { - const _EmailTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final valid = context.select((SignUpBloc bloc) => bloc.state.emailIsValid); - - return TextField( - key: const Key('SignUpEmailTextField'), - onChanged: (value) => - context.read().add(SignUpEmailChanged(value)), - decoration: InputDecoration( - hintText: l10n.email, - errorText: valid ? null : l10n.invalidEmail, - ), - keyboardType: TextInputType.emailAddress, - ); - } -} - -class _PasswordTextField extends StatelessWidget { - const _PasswordTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final obscurePassword = - context.select((SignUpBloc bloc) => bloc.state.obscurePasswords); - final valid = - context.select((SignUpBloc bloc) => bloc.state.passwordIsValid); - - return TextField( - key: const Key('SignUpPasswordTextField'), - autocorrect: false, - keyboardType: TextInputType.visiblePassword, - onChanged: (password) => context - .read() - .add(SignUpPasswordChanged(password: password)), - decoration: InputDecoration( - hintText: l10n.password, - errorText: valid ? null : l10n.signUpPasswordRequirement, - suffixIcon: IconButton( - icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), - onPressed: () { - context.read().add( - SignUpPasswordVisibilityChanged(obscure: !obscurePassword), - ); - }, - ), - ), - enableIMEPersonalizedLearning: false, - obscureText: obscurePassword, - ); - } -} - -class _ConfirmationPasswordTextField extends StatelessWidget { - const _ConfirmationPasswordTextField(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final obscurePassword = - context.select((SignUpBloc bloc) => bloc.state.obscurePasswords); - final valid = - context.select((SignUpBloc bloc) => bloc.state.passwordsMatch); - - return TextField( - key: const Key('SignUpConfirmationPasswordTextField'), - autocorrect: false, - keyboardType: TextInputType.visiblePassword, - onChanged: (password) => context.read().add( - SignUpConfirmationPasswordChanged(password: password), - ), - decoration: InputDecoration( - hintText: l10n.signUpPasswordConfirmation, - errorText: valid ? null : l10n.signUpPasswordMismatch, - suffixIcon: IconButton( - icon: Icon(obscurePassword ? Icons.visibility : Icons.visibility_off), - onPressed: () { - context.read().add( - SignUpPasswordVisibilityChanged(obscure: !obscurePassword), - ); - }, - ), - ), - enableIMEPersonalizedLearning: false, - obscureText: obscurePassword, - ); - } -} - -class _SignUpButton extends StatelessWidget { - const _SignUpButton(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final validToSubmit = context.select((SignUpBloc bloc) => bloc.state.valid); - - return ElevatedButton( - onPressed: validToSubmit - ? () { - context - .read() - .add(const SignUpWithEmailAndPasswordRequested()); - } - : null, - child: Text(l10n.signUp), - ); - } -} diff --git a/todo/lib/authentication/sign_up/view/view.dart b/todo/lib/authentication/sign_up/view/view.dart deleted file mode 100644 index 31406a5..0000000 --- a/todo/lib/authentication/sign_up/view/view.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'sign_up_page.dart'; -export 'sign_up_view.dart'; diff --git a/todo/lib/down_for_maintenance/down_for_maintenance.dart b/todo/lib/down_for_maintenance/down_for_maintenance.dart deleted file mode 100644 index 2fd64b2..0000000 --- a/todo/lib/down_for_maintenance/down_for_maintenance.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'down_for_maintenance_page.dart'; -export 'down_for_maintenance_view.dart'; diff --git a/todo/lib/down_for_maintenance/down_for_maintenance_page.dart b/todo/lib/down_for_maintenance/down_for_maintenance_page.dart deleted file mode 100644 index 56772a8..0000000 --- a/todo/lib/down_for_maintenance/down_for_maintenance_page.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; - -class DownForMaintenancePage extends Page { - const DownForMaintenancePage({super.key}); - static const path = '/down-for-maintenance'; - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - settings: this, - builder: (context) { - return const DownForMaintenanceView(); - }, - ); - } -} diff --git a/todo/lib/down_for_maintenance/down_for_maintenance_view.dart b/todo/lib/down_for_maintenance/down_for_maintenance_view.dart deleted file mode 100644 index 958138c..0000000 --- a/todo/lib/down_for_maintenance/down_for_maintenance_view.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:todo/l10n/l10n.dart'; - -class DownForMaintenanceView extends StatelessWidget { - const DownForMaintenanceView({super.key}); - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return Scaffold( - body: Center( - child: Text(l10n.downForMaintenanceMessage), - ), - ); - } - - static void showAsDialog(BuildContext context) { - showAdaptiveDialog( - context: context, - builder: (context) { - final l10n = context.l10n; - return AlertDialog.adaptive( - title: Text(l10n.downForMaintenance), - content: Text( - l10n.downForMaintenanceMessage, - ), - actions: [ - TextButton( - onPressed: SystemNavigator.pop, - child: Text(l10n.ok), - ), - ], - ); - }, - ); - } -} diff --git a/todo/lib/force_upgrade/force_upgrade.dart b/todo/lib/force_upgrade/force_upgrade.dart deleted file mode 100644 index 6a6d060..0000000 --- a/todo/lib/force_upgrade/force_upgrade.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'force_upgrade_page.dart'; -export 'force_upgrade_view.dart'; diff --git a/todo/lib/force_upgrade/force_upgrade_page.dart b/todo/lib/force_upgrade/force_upgrade_page.dart deleted file mode 100644 index 59d22dd..0000000 --- a/todo/lib/force_upgrade/force_upgrade_page.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo/force_upgrade/force_upgrade.dart'; - -class ForceUpgradePage extends Page { - const ForceUpgradePage({super.key}); - static const path = '/force-upgrade'; - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - settings: this, - builder: (context) { - return const ForceUpgradeView(); - }, - ); - } -} diff --git a/todo/lib/force_upgrade/force_upgrade_view.dart b/todo/lib/force_upgrade/force_upgrade_view.dart deleted file mode 100644 index e49f7f3..0000000 --- a/todo/lib/force_upgrade/force_upgrade_view.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo/l10n/l10n.dart'; - -class ForceUpgradeView extends StatelessWidget { - const ForceUpgradeView({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - return Scaffold( - body: Center( - child: Text( - l10n.forceUpgradeMessage, - ), - ), - ); - } - - static void showAsDialog(BuildContext context) { - showAdaptiveDialog( - context: context, - builder: (context) { - final l10n = context.l10n; - return AlertDialog.adaptive( - title: Text(l10n.updateRequired), - content: Text( - l10n.forceUpgradeMessage, - ), - actions: [ - TextButton( - onPressed: () {}, - child: Text(l10n.ok), - ), - ], - ); - }, - ); - } -} diff --git a/todo/lib/home/bloc/home_bloc.dart b/todo/lib/home/bloc/home_bloc.dart deleted file mode 100644 index 8794675..0000000 --- a/todo/lib/home/bloc/home_bloc.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; - -part 'home_event.dart'; -part 'home_state.dart'; - -class HomeBloc extends Bloc { - HomeBloc() : super(const HomeState.initial()) { - on((event, emit) { - emit(state.copyWith(tabIndex: event.tabIndex)); - }); - } -} diff --git a/todo/lib/home/bloc/home_event.dart b/todo/lib/home/bloc/home_event.dart deleted file mode 100644 index 95216ef..0000000 --- a/todo/lib/home/bloc/home_event.dart +++ /dev/null @@ -1,17 +0,0 @@ -part of 'home_bloc.dart'; - -class HomeEvent extends Equatable { - const HomeEvent(); - - @override - List get props => []; -} - -class HomeTabChanged extends HomeEvent { - const HomeTabChanged(this.tabIndex); - - final int tabIndex; - - @override - List get props => [tabIndex]; -} diff --git a/todo/lib/home/bloc/home_state.dart b/todo/lib/home/bloc/home_state.dart deleted file mode 100644 index 399d220..0000000 --- a/todo/lib/home/bloc/home_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -part of 'home_bloc.dart'; - -enum HomeStatus { - initial, - loading; - - bool get isLoading => this == HomeStatus.loading; -} - -class HomeState extends Equatable { - const HomeState({required this.status, this.tabIndex = 0}); - - const HomeState.initial() - : status = HomeStatus.initial, - tabIndex = 0; - - final HomeStatus status; - final int tabIndex; - - HomeState copyWith({HomeStatus? status, int? tabIndex}) { - return HomeState( - status: status ?? this.status, - tabIndex: tabIndex ?? this.tabIndex, - ); - } - - @override - List get props => [status, tabIndex]; -} diff --git a/todo/lib/home/home.dart b/todo/lib/home/home.dart deleted file mode 100644 index 3468ef3..0000000 --- a/todo/lib/home/home.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/home_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/home/view/home_page.dart b/todo/lib/home/view/home_page.dart deleted file mode 100644 index c62dcd0..0000000 --- a/todo/lib/home/view/home_page.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/home/home.dart'; - -class HomePage extends Page { - const HomePage(); - static const path = '/home'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - fullscreenDialog: true, - settings: this, - builder: (ctx) { - return BlocProvider( - create: (_) => HomeBloc(), - child: const HomeView(), - ); - }, - ); - } -} diff --git a/todo/lib/home/view/home_view.dart b/todo/lib/home/view/home_view.dart deleted file mode 100644 index 962b815..0000000 --- a/todo/lib/home/view/home_view.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/authentication/delete_account/view/delete_account_page.dart'; -import 'package:todo/home/home.dart'; -import 'package:todo/l10n/l10n.dart'; - -class HomeView extends StatelessWidget { - const HomeView({super.key}); - - @override - Widget build(BuildContext context) { - final status = context.select((HomeBloc bloc) => bloc.state.status); - final l10n = context.l10n; - - return Scaffold( - appBar: AppBar( - key: const Key('HomeAppBar'), - title: Text(l10n.home), - actions: [ - IconButton( - key: const Key('HomeDeleteAccountButton'), - icon: Text(l10n.deleteAccount), - onPressed: () { - context.push(DeleteAccountPage.path); - }, - ), - IconButton( - key: const Key('HomeLogoutButton'), - icon: Text(l10n.logOut), - onPressed: () { - showLogoutConfirmationDialog(context); - }, - ), - ], - ), - body: status.isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : const Placeholder(), - ); - } -} - -@visibleForTesting -void showLogoutConfirmationDialog(BuildContext context) { - final l10n = context.l10n; - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Text(l10n.logOutConfirmation), - actions: [ - TextButton( - key: const Key('CancelButton'), - child: Text(l10n.cancel), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); -} diff --git a/todo/lib/home/view/main_screen_page_builder.dart b/todo/lib/home/view/main_screen_page_builder.dart deleted file mode 100644 index 1e169f8..0000000 --- a/todo/lib/home/view/main_screen_page_builder.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/home/home.dart'; -import 'package:todo/navigation/navigation.dart'; -import 'package:todo/settings/settings.dart'; - -class MainScreenPageBuilder extends StatelessWidget { - const MainScreenPageBuilder({ - required this.child, - this.currentIndex, - super.key, - }); - - static NoTransitionPage route(Widget child, int? currentIndex) => - NoTransitionPage( - child: BlocProvider( - create: (context) => HomeBloc(), - child: MainScreenPageBuilder( - currentIndex: currentIndex, - child: child, - ), - ), - ); - - final Widget child; - final int? currentIndex; - - @override - Widget build(BuildContext context) { - final selectedTab = context.select((HomeBloc bloc) => bloc.state.tabIndex); - - return Scaffold( - body: child, - bottomNavigationBar: BottomNavBar( - currentIndex: selectedTab, - onTap: (index) { - context.read().add(HomeTabChanged(index)); - if (index == 0) { - context.go(HomePage.path); - } - if (index == 1) { - context.go(SettingsPage.path); - } - }, - ), - ); - } -} diff --git a/todo/lib/home/view/view.dart b/todo/lib/home/view/view.dart deleted file mode 100644 index 8093906..0000000 --- a/todo/lib/home/view/view.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'home_page.dart'; -export 'home_view.dart'; -export 'main_screen_page_builder.dart'; diff --git a/todo/lib/l10n/arb/app_en.arb b/todo/lib/l10n/arb/app_en.arb deleted file mode 100644 index b939b2d..0000000 --- a/todo/lib/l10n/arb/app_en.arb +++ /dev/null @@ -1,131 +0,0 @@ -{ - "@@locale": "en", - "home": "Home", - "@home": { - "description": "Text shown on the home page" - }, - "logInSuccessful": "Log in successful", - "@logInSuccessful": { - "description": "Text shown when the user logs in successfully" - }, - "logIn": "Log in", - "@logIn": { - "description": "Text shown on the log in button" - }, - "password": "Password", - "@password": { - "description": "Text shown on the password input" - }, - "email": "Email", - "@email": { - "description": "Text shown on the email input" - }, - "invalidEmail": "Please enter a valid email.", - "@invalidEmail": { - "description": "Text shown when the user enters an invalid email" - }, - "unknownError": "Something went wrong.", - "@unknownError": { - "description": "Text shown when an unknown error occurs" - }, - "signUpPasswordRequirement": "Password must be at least 6 characters long.", - "@signUpPasswordRequirement": { - "description": "Text shown when the user enters a password that is too short" - }, - "signUpPasswordConfirmation": "Password confirmation", - "@signUpPasswordConfirmation": { - "description": "Text shown on the password confirmation input" - }, - "signUpPasswordMismatch": "Passwords do not match.", - "@signUpPasswordMismatch": { - "description": "Text shown when the user enters two different passwords" - }, - "signUp": "Sign up", - "@signUp": { - "description": "Text shown on the sign up button" - }, - "signUpSuccessful": "Sign up successful.", - "@signUpSuccessful": { - "description": "Text shown when the user signs up successfully" - }, - "forgotPassword": "Forgot password?", - "@forgotPassword": { - "description": "Text shown on the forgot password button" - }, - "forgotPasswordDescription": "Please enter your email. We will send you a link to reset your password.", - "@forgotPasswordDescription": { - "description": "Text shown on the forgot password description" - }, - "reset": "Reset", - "@reset": { - "description": "Text shown on the reset button" - }, - "resetSuccessful": "Password reset email sent succesfully.", - "@resetSuccessful": { - "description": "Text shown when the user resets their password successfully" - }, - "signUpName": "Name", - "@signUpName": { - "description": "Text shown on the name input" - }, - "signUpNameInvalid": "Invalid name format. Please enter a valid name.", - "@signUpNameInvalid": { - "description": "Text shown when the user enters an invalid name" - }, - "logOut": "Log out", - "@logout": { - "description": "Text shown on the log out button" - }, - "logOutConfirmation": "Are you sure you want to log out?", - "@logOutConfirmation": { - "description": "Text shown on the log out confirmation" - }, - "cancel": "Cancel", - "@cancel": { - "description": "Text shown on the cancel button" - }, - "deleteAccount": "Delete account", - "@deleteAccount": { - "description": "Text shown on the delete account button" - }, - "downForMaintenanceMessage": "The app is currently down for maintenance. Please try again later.", - "@downForMaintenanceMessage": { - "description": "Message shown when the app is down for maintenance" - }, - "downForMaintenance": "Down For Maintenance", - "@downForMaintenance": { - "description": "Message shown when the app is down for maintenance" - }, - "forceUpgradeMessage": "The app is out of date. Please update to the latest version.", - "@forceUpgradeMessage": { - "description": "Message shown when the app must be updated" - }, - "updateRequired": "Update Required", - "@updateRequired": { - "description": "Message shown when the app must be updated" - }, - "ok":"Ok", - "@ok": { - "description": "Confirm text for a button" - }, - "settings": "Settings", - "@settings": { - "description": "Text shown on the settings button" - }, - "termsAndConditions": "Terms and Conditions", - "@termsAndConditions": { - "description": "Text shown on the terms and conditions button" - }, - "aboutUs": "About Us", - "@aboutUs": { - "description": "Text shown on the about us button" - }, - "version": "Version", - "@version": { - "description": "Text shown on the version label" - }, - "contactUs": "Contact Us", - "@contactUs": { - "description": "Text shown on the contact us button" - } -} \ No newline at end of file diff --git a/todo/lib/l10n/arb/app_localizations.dart b/todo/lib/l10n/arb/app_localizations.dart deleted file mode 100644 index 5dc8611..0000000 --- a/todo/lib/l10n/arb/app_localizations.dart +++ /dev/null @@ -1,319 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:intl/intl.dart' as intl; - -import 'app_localizations_en.dart'; - -// ignore_for_file: type=lint - -/// Callers can lookup localized strings with an instance of AppLocalizations -/// returned by `AppLocalizations.of(context)`. -/// -/// Applications need to include `AppLocalizations.delegate()` in their app's -/// `localizationDelegates` list, and the locales they support in the app's -/// `supportedLocales` list. For example: -/// -/// ```dart -/// import 'arb/app_localizations.dart'; -/// -/// return MaterialApp( -/// localizationsDelegates: AppLocalizations.localizationsDelegates, -/// supportedLocales: AppLocalizations.supportedLocales, -/// home: MyApplicationHome(), -/// ); -/// ``` -/// -/// ## Update pubspec.yaml -/// -/// Please make sure to update your pubspec.yaml to include the following -/// packages: -/// -/// ```yaml -/// dependencies: -/// # Internationalization support. -/// flutter_localizations: -/// sdk: flutter -/// intl: any # Use the pinned version from flutter_localizations -/// -/// # Rest of dependencies -/// ``` -/// -/// ## iOS Applications -/// -/// iOS applications define key application metadata, including supported -/// locales, in an Info.plist file that is built into the application bundle. -/// To configure the locales supported by your app, you’ll need to edit this -/// file. -/// -/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. -/// Then, in the Project Navigator, open the Info.plist file under the Runner -/// project’s Runner folder. -/// -/// Next, select the Information Property List item, select Add Item from the -/// Editor menu, then select Localizations from the pop-up menu. -/// -/// Select and expand the newly-created Localizations item then, for each -/// locale your application supports, add a new item and select the locale -/// you wish to add from the pop-up menu in the Value field. This list should -/// be consistent with the languages listed in the AppLocalizations.supportedLocales -/// property. -abstract class AppLocalizations { - AppLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); - - final String localeName; - - static AppLocalizations of(BuildContext context) { - return Localizations.of(context, AppLocalizations)!; - } - - static const LocalizationsDelegate delegate = - _AppLocalizationsDelegate(); - - /// A list of this localizations delegate along with the default localizations - /// delegates. - /// - /// Returns a list of localizations delegates containing this delegate along with - /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, - /// and GlobalWidgetsLocalizations.delegate. - /// - /// Additional delegates can be added by appending to this list in - /// MaterialApp. This list does not have to be used at all if a custom list - /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; - - /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [Locale('en')]; - - /// Text shown on the home page - /// - /// In en, this message translates to: - /// **'Home'** - String get home; - - /// Text shown when the user logs in successfully - /// - /// In en, this message translates to: - /// **'Log in successful'** - String get logInSuccessful; - - /// Text shown on the log in button - /// - /// In en, this message translates to: - /// **'Log in'** - String get logIn; - - /// Text shown on the password input - /// - /// In en, this message translates to: - /// **'Password'** - String get password; - - /// Text shown on the email input - /// - /// In en, this message translates to: - /// **'Email'** - String get email; - - /// Text shown when the user enters an invalid email - /// - /// In en, this message translates to: - /// **'Please enter a valid email.'** - String get invalidEmail; - - /// Text shown when an unknown error occurs - /// - /// In en, this message translates to: - /// **'Something went wrong.'** - String get unknownError; - - /// Text shown when the user enters a password that is too short - /// - /// In en, this message translates to: - /// **'Password must be at least 6 characters long.'** - String get signUpPasswordRequirement; - - /// Text shown on the password confirmation input - /// - /// In en, this message translates to: - /// **'Password confirmation'** - String get signUpPasswordConfirmation; - - /// Text shown when the user enters two different passwords - /// - /// In en, this message translates to: - /// **'Passwords do not match.'** - String get signUpPasswordMismatch; - - /// Text shown on the sign up button - /// - /// In en, this message translates to: - /// **'Sign up'** - String get signUp; - - /// Text shown when the user signs up successfully - /// - /// In en, this message translates to: - /// **'Sign up successful.'** - String get signUpSuccessful; - - /// Text shown on the forgot password button - /// - /// In en, this message translates to: - /// **'Forgot password?'** - String get forgotPassword; - - /// Text shown on the forgot password description - /// - /// In en, this message translates to: - /// **'Please enter your email. We will send you a link to reset your password.'** - String get forgotPasswordDescription; - - /// Text shown on the reset button - /// - /// In en, this message translates to: - /// **'Reset'** - String get reset; - - /// Text shown when the user resets their password successfully - /// - /// In en, this message translates to: - /// **'Password reset email sent succesfully.'** - String get resetSuccessful; - - /// Text shown on the name input - /// - /// In en, this message translates to: - /// **'Name'** - String get signUpName; - - /// Text shown when the user enters an invalid name - /// - /// In en, this message translates to: - /// **'Invalid name format. Please enter a valid name.'** - String get signUpNameInvalid; - - /// No description provided for @logOut. - /// - /// In en, this message translates to: - /// **'Log out'** - String get logOut; - - /// Text shown on the log out confirmation - /// - /// In en, this message translates to: - /// **'Are you sure you want to log out?'** - String get logOutConfirmation; - - /// Text shown on the cancel button - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Text shown on the delete account button - /// - /// In en, this message translates to: - /// **'Delete account'** - String get deleteAccount; - - /// Message shown when the app is down for maintenance - /// - /// In en, this message translates to: - /// **'The app is currently down for maintenance. Please try again later.'** - String get downForMaintenanceMessage; - - /// Message shown when the app is down for maintenance - /// - /// In en, this message translates to: - /// **'Down For Maintenance'** - String get downForMaintenance; - - /// Message shown when the app must be updated - /// - /// In en, this message translates to: - /// **'The app is out of date. Please update to the latest version.'** - String get forceUpgradeMessage; - - /// Message shown when the app must be updated - /// - /// In en, this message translates to: - /// **'Update Required'** - String get updateRequired; - - /// Confirm text for a button - /// - /// In en, this message translates to: - /// **'Ok'** - String get ok; - - /// Text shown on the settings button - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Text shown on the terms and conditions button - /// - /// In en, this message translates to: - /// **'Terms and Conditions'** - String get termsAndConditions; - - /// Text shown on the about us button - /// - /// In en, this message translates to: - /// **'About Us'** - String get aboutUs; - - /// Text shown on the version label - /// - /// In en, this message translates to: - /// **'Version'** - String get version; - - /// Text shown on the contact us button - /// - /// In en, this message translates to: - /// **'Contact Us'** - String get contactUs; -} - -class _AppLocalizationsDelegate - extends LocalizationsDelegate { - const _AppLocalizationsDelegate(); - - @override - Future load(Locale locale) { - return SynchronousFuture(lookupAppLocalizations(locale)); - } - - @override - bool isSupported(Locale locale) => - ['en'].contains(locale.languageCode); - - @override - bool shouldReload(_AppLocalizationsDelegate old) => false; -} - -AppLocalizations lookupAppLocalizations(Locale locale) { - // Lookup logic when only language code is specified. - switch (locale.languageCode) { - case 'en': - return AppLocalizationsEn(); - } - - throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.'); -} diff --git a/todo/lib/l10n/arb/app_localizations_en.dart b/todo/lib/l10n/arb/app_localizations_en.dart deleted file mode 100644 index de9387d..0000000 --- a/todo/lib/l10n/arb/app_localizations_en.dart +++ /dev/null @@ -1,111 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for English (`en`). -class AppLocalizationsEn extends AppLocalizations { - AppLocalizationsEn([String locale = 'en']) : super(locale); - - @override - String get home => 'Home'; - - @override - String get logInSuccessful => 'Log in successful'; - - @override - String get logIn => 'Log in'; - - @override - String get password => 'Password'; - - @override - String get email => 'Email'; - - @override - String get invalidEmail => 'Please enter a valid email.'; - - @override - String get unknownError => 'Something went wrong.'; - - @override - String get signUpPasswordRequirement => - 'Password must be at least 6 characters long.'; - - @override - String get signUpPasswordConfirmation => 'Password confirmation'; - - @override - String get signUpPasswordMismatch => 'Passwords do not match.'; - - @override - String get signUp => 'Sign up'; - - @override - String get signUpSuccessful => 'Sign up successful.'; - - @override - String get forgotPassword => 'Forgot password?'; - - @override - String get forgotPasswordDescription => - 'Please enter your email. We will send you a link to reset your password.'; - - @override - String get reset => 'Reset'; - - @override - String get resetSuccessful => 'Password reset email sent succesfully.'; - - @override - String get signUpName => 'Name'; - - @override - String get signUpNameInvalid => - 'Invalid name format. Please enter a valid name.'; - - @override - String get logOut => 'Log out'; - - @override - String get logOutConfirmation => 'Are you sure you want to log out?'; - - @override - String get cancel => 'Cancel'; - - @override - String get deleteAccount => 'Delete account'; - - @override - String get downForMaintenanceMessage => - 'The app is currently down for maintenance. Please try again later.'; - - @override - String get downForMaintenance => 'Down For Maintenance'; - - @override - String get forceUpgradeMessage => - 'The app is out of date. Please update to the latest version.'; - - @override - String get updateRequired => 'Update Required'; - - @override - String get ok => 'Ok'; - - @override - String get settings => 'Settings'; - - @override - String get termsAndConditions => 'Terms and Conditions'; - - @override - String get aboutUs => 'About Us'; - - @override - String get version => 'Version'; - - @override - String get contactUs => 'Contact Us'; -} diff --git a/todo/lib/l10n/l10n.dart b/todo/lib/l10n/l10n.dart deleted file mode 100644 index 9bb5d4b..0000000 --- a/todo/lib/l10n/l10n.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:todo/l10n/arb/app_localizations.dart'; - -export 'package:todo/l10n/arb/app_localizations.dart'; -extension AppLocalizationsX on BuildContext { - AppLocalizations get l10n => AppLocalizations.of(this); -} diff --git a/todo/lib/main/bootstrap.dart b/todo/lib/main/bootstrap.dart deleted file mode 100644 index 558f731..0000000 --- a/todo/lib/main/bootstrap.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; -import 'dart:developer'; - -import 'package:bloc/bloc.dart'; -import 'package:flutter/widgets.dart'; - -class AppBlocObserver extends BlocObserver { - const AppBlocObserver(); - - @override - void onChange(BlocBase bloc, Change change) { - super.onChange(bloc, change); - log('onChange(${bloc.runtimeType}, $change)'); - } - - @override - void onError(BlocBase bloc, Object error, StackTrace stackTrace) { - log('onError(${bloc.runtimeType}, $error, $stackTrace)'); - super.onError(bloc, error, stackTrace); - } -} - -Future bootstrap(FutureOr Function() builder) async { - FlutterError.onError = (details) { - log(details.exceptionAsString(), stackTrace: details.stack); - }; - - Bloc.observer = const AppBlocObserver(); - - // Add cross-flavor configuration here - - runApp(await builder()); -} diff --git a/todo/lib/main/main.dart b/todo/lib/main/main.dart deleted file mode 100644 index ab4d882..0000000 --- a/todo/lib/main/main.dart +++ /dev/null @@ -1 +0,0 @@ -export 'bootstrap.dart'; diff --git a/todo/lib/main/main_common.dart b/todo/lib/main/main_common.dart deleted file mode 100644 index d5f7944..0000000 --- a/todo/lib/main/main_common.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:auth_client/auth_client.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -Future mainCommon({ - required String apiUrl, -}) async { - final authClient = AuthenticationClient(); - final client = HttpApiClient( - apiUrl: apiUrl, - apiKey: 'apiKey', - tokenProvider: authClient, - ); - final apiClient = ApiClient(client: client); - final userRepository = UserRepository( - apiClient: apiClient, - authenticationClient: authClient, - ); - - final user = await userRepository.user.first; - - final appConfigRepository = AppConfigRepository( - buildNumber: 1, - platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, - apiClient: apiClient, - ); - - return App( - user: user, - userRepository: userRepository, - appConfigRepository: appConfigRepository, - ); -} diff --git a/todo/lib/main/main_development.dart b/todo/lib/main/main_development.dart deleted file mode 100644 index 0ef81c3..0000000 --- a/todo/lib/main/main_development.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:todo/main/main.dart'; -import 'package:todo/main/main_common.dart'; - -void main() { - bootstrap( - () { - const apiUrl = 'https://api.example.com'; - - return mainCommon( - apiUrl: apiUrl, - ); - }, - ); -} diff --git a/todo/lib/main/main_production.dart b/todo/lib/main/main_production.dart deleted file mode 100644 index 0ef81c3..0000000 --- a/todo/lib/main/main_production.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:todo/main/main.dart'; -import 'package:todo/main/main_common.dart'; - -void main() { - bootstrap( - () { - const apiUrl = 'https://api.example.com'; - - return mainCommon( - apiUrl: apiUrl, - ); - }, - ); -} diff --git a/todo/lib/main/main_staging.dart b/todo/lib/main/main_staging.dart deleted file mode 100644 index 0ef81c3..0000000 --- a/todo/lib/main/main_staging.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:todo/main/main.dart'; -import 'package:todo/main/main_common.dart'; - -void main() { - bootstrap( - () { - const apiUrl = 'https://api.example.com'; - - return mainCommon( - apiUrl: apiUrl, - ); - }, - ); -} diff --git a/todo/lib/navigation/navigation.dart b/todo/lib/navigation/navigation.dart deleted file mode 100644 index 00ffcf9..0000000 --- a/todo/lib/navigation/navigation.dart +++ /dev/null @@ -1 +0,0 @@ -export 'view/view.dart'; diff --git a/todo/lib/navigation/view/bottom_nav_bar.dart b/todo/lib/navigation/view/bottom_nav_bar.dart deleted file mode 100644 index e1c74ab..0000000 --- a/todo/lib/navigation/view/bottom_nav_bar.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo/l10n/l10n.dart'; - -@visibleForTesting -class BottomNavBar extends StatelessWidget { - const BottomNavBar({ - required this.currentIndex, - required this.onTap, - super.key, - }); - - final int currentIndex; - final ValueSetter onTap; - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.home_outlined), - label: l10n.home, - ), - BottomNavigationBarItem( - icon: const Icon(Icons.settings_outlined), - label: l10n.settings, - ), - ], - currentIndex: currentIndex, - onTap: onTap, - ); - } -} diff --git a/todo/lib/navigation/view/view.dart b/todo/lib/navigation/view/view.dart deleted file mode 100644 index ef3bbc9..0000000 --- a/todo/lib/navigation/view/view.dart +++ /dev/null @@ -1 +0,0 @@ -export 'bottom_nav_bar.dart'; diff --git a/todo/lib/settings/bloc/settings_bloc.dart b/todo/lib/settings/bloc/settings_bloc.dart deleted file mode 100644 index 9073d5e..0000000 --- a/todo/lib/settings/bloc/settings_bloc.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; - -part 'settings_event.dart'; -part 'settings_state.dart'; - -class SettingsBloc extends Bloc { - SettingsBloc() : super(const SettingsState.initial()) { - on(_onVersionNumberRetrieved); - on(_onAboutUsRetrieved); - on(_onTermsAndConditionsRetrieved); - } - - Future _onVersionNumberRetrieved( - VersionNumberRetrieved event, - Emitter emit, - ) async { - try { - final platformInfo = await PackageInfo.fromPlatform(); - - emit( - state.copyWith( - appVersion: '${platformInfo.version}+${platformInfo.buildNumber}', - ), - ); - } on Exception { - emit(state.copyWith(status: SettingsStatus.error)); - } - } - - Future _onTermsAndConditionsRetrieved( - TermsAndConditionsRetrieved event, - Emitter emit, - ) async { - try { - final termsAndConditionsUrl = Uri( - scheme: 'https', - host: 'pub.dev', - ); - final canLaunch = await canLaunchUrl(termsAndConditionsUrl); - if (canLaunch) { - await launchUrl(termsAndConditionsUrl); - } - } catch (e) { - emit(state.copyWith(status: SettingsStatus.error)); - } - } - - Future _onAboutUsRetrieved( - AboutUsRetrieved event, - Emitter emit, - ) async { - try { - final aboutUsUrl = Uri( - scheme: 'https', - host: 'pub.dev', - ); - final canLaunch = await canLaunchUrl(aboutUsUrl); - if (canLaunch) { - await launchUrl(aboutUsUrl); - } - } catch (e) { - emit(state.copyWith(status: SettingsStatus.error)); - } - } -} diff --git a/todo/lib/settings/bloc/settings_event.dart b/todo/lib/settings/bloc/settings_event.dart deleted file mode 100644 index 7fde87e..0000000 --- a/todo/lib/settings/bloc/settings_event.dart +++ /dev/null @@ -1,20 +0,0 @@ -part of 'settings_bloc.dart'; - -abstract class SettingsEvent extends Equatable { - const SettingsEvent(); - - @override - List get props => []; -} - -class VersionNumberRetrieved extends SettingsEvent { - const VersionNumberRetrieved(); -} - -class TermsAndConditionsRetrieved extends SettingsEvent { - const TermsAndConditionsRetrieved(); -} - -class AboutUsRetrieved extends SettingsEvent { - const AboutUsRetrieved(); -} diff --git a/todo/lib/settings/bloc/settings_state.dart b/todo/lib/settings/bloc/settings_state.dart deleted file mode 100644 index 9731798..0000000 --- a/todo/lib/settings/bloc/settings_state.dart +++ /dev/null @@ -1,40 +0,0 @@ -part of 'settings_bloc.dart'; - -enum SettingsStatus { - initial, - loading, - loaded, - error, -} - -class SettingsState extends Equatable { - const SettingsState({ - required this.status, - required this.appVersion, - }); - - const SettingsState.initial() - : this( - status: SettingsStatus.initial, - appVersion: '', - ); - - final SettingsStatus status; - final String appVersion; - - SettingsState copyWith({ - SettingsStatus? status, - String? appVersion, - }) { - return SettingsState( - status: status ?? this.status, - appVersion: appVersion ?? this.appVersion, - ); - } - - @override - List get props => [ - status, - appVersion, - ]; -} diff --git a/todo/lib/settings/settings.dart b/todo/lib/settings/settings.dart deleted file mode 100644 index 9e1b97c..0000000 --- a/todo/lib/settings/settings.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bloc/settings_bloc.dart'; -export 'view/view.dart'; diff --git a/todo/lib/settings/view/settings_page.dart b/todo/lib/settings/view/settings_page.dart deleted file mode 100644 index 168f857..0000000 --- a/todo/lib/settings/view/settings_page.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:todo/settings/settings.dart'; - -class SettingsPage extends Page { - const SettingsPage(); - static const path = '/settings'; - - @override - Route createRoute(BuildContext context) { - return MaterialPageRoute( - fullscreenDialog: true, - settings: this, - builder: (ctx) { - return BlocProvider( - create: (_) => SettingsBloc() - ..add( - const VersionNumberRetrieved(), - ), - child: const SettingsView(), - ); - }, - ); - } -} diff --git a/todo/lib/settings/view/settings_view.dart b/todo/lib/settings/view/settings_view.dart deleted file mode 100644 index d0f298d..0000000 --- a/todo/lib/settings/view/settings_view.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:todo/app/app.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo/settings/settings.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class SettingsView extends StatelessWidget { - const SettingsView({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return BlocListener( - listener: (context, state) { - if (state.status == SettingsStatus.error) { - // Handle Error - Navigator.of(context).pop(); - } - }, - child: Scaffold( - appBar: AppBar( - key: const Key('SettingsAppBar'), - title: Text(l10n.settings), - ), - body: const _SettingsForm(), - ), - ); - } -} - -class _SettingsForm extends StatelessWidget { - const _SettingsForm(); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final status = context.select((SettingsBloc bloc) => bloc.state.status); - final appVersion = - context.select((SettingsBloc bloc) => bloc.state.appVersion); - - if (status == SettingsStatus.loading) { - return const CircularProgressIndicator(); - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _SettingsItem( - key: const Key('ContactUsSettingsItem'), - title: l10n.contactUs, - onTap: () {}, - ), - _SettingsItem( - key: const Key('TermsAndConditionsSettingsItem'), - title: l10n.termsAndConditions, - onTap: () {}, - ), - _SettingsItem( - key: const Key('AboutUsSettingsItem'), - title: l10n.aboutUs, - onTap: () {}, - ), - _SettingsItem( - key: const Key('LogoutSettingsItem'), - title: l10n.logOut, - onTap: () { - context.read().add(const AppLogoutRequested()); - Navigator.of(context).pop(); - }, - ), - _SettingsItem( - key: const Key('DeleteAccountSettingsItem'), - title: l10n.deleteAccount, - onTap: () { - context.push(DeleteAccountPage.path); - }, - ), - Padding( - key: const Key('AppVersionSettingsItem'), - padding: const EdgeInsets.all(UISpacing.xlg), - child: Text('${l10n.version} $appVersion'), - ), - ], - ); - } -} - -class _SettingsItem extends StatelessWidget { - const _SettingsItem({ - required this.title, - required this.onTap, - super.key, - }); - - final String title; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: UISpacing.xxxlg, - child: ListTile( - title: Text(title), - onTap: onTap, - ), - ); - } -} diff --git a/todo/lib/settings/view/view.dart b/todo/lib/settings/view/view.dart deleted file mode 100644 index 0caee92..0000000 --- a/todo/lib/settings/view/view.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'settings_page.dart'; -export 'settings_view.dart'; diff --git a/todo/packages/api_client/.gitignore b/todo/packages/api_client/.gitignore deleted file mode 100644 index 526da15..0000000 --- a/todo/packages/api_client/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# See https://www.dartlang.org/guides/libraries/private-files - -# Files and directories created by pub -.dart_tool/ -.packages -build/ -pubspec.lock \ No newline at end of file diff --git a/todo/packages/api_client/analysis_options.yaml b/todo/packages/api_client/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/api_client/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/api_client/lib/api_client.dart b/todo/packages/api_client/lib/api_client.dart deleted file mode 100644 index e2658ac..0000000 --- a/todo/packages/api_client/lib/api_client.dart +++ /dev/null @@ -1,7 +0,0 @@ -/// A client to communicate with todo backend -library api_client; - -export 'src/api_client.dart'; -export 'src/http_api_client.dart'; -export 'src/resources/resources.dart'; -export 'src/utils/utils.dart'; diff --git a/todo/packages/api_client/lib/src/api_client.dart b/todo/packages/api_client/lib/src/api_client.dart deleted file mode 100644 index 9ba8782..0000000 --- a/todo/packages/api_client/lib/src/api_client.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:api_client/api_client.dart'; - -/// {@template api_client} -/// A client to communicate with todo backend -/// {@endtemplate} -class ApiClient { - /// {@macro api_client} - ApiClient({ - required HttpApiClient client, - }) : _client = client; - - final HttpApiClient _client; - - /// {@macro user_resource} - UserResource get userResource { - return UserResource( - client: _client, - ); - } - - /// {@macro app_config_resource} - AppConfigResource get appConfigResource { - return AppConfigResource( - client: _client, - ); - } -} diff --git a/todo/packages/api_client/lib/src/http_api_client.dart b/todo/packages/api_client/lib/src/http_api_client.dart deleted file mode 100644 index b6b6436..0000000 --- a/todo/packages/api_client/lib/src/http_api_client.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'dart:io'; - -import 'package:equatable/equatable.dart'; -import 'package:http/http.dart'; -import 'package:token_provider/token_provider.dart'; - -/// {@template api_request_options} -/// Provide optional fields to be passed for [HttpApiClient] requests. -/// {@endtemplate} -class ApiRequestOptions extends Equatable { - /// {@macro api_request_options} - const ApiRequestOptions({this.headers}); - - /// Json content type header. - static const json = ApiRequestOptions( - headers: {'Content-Type': 'application/json'}, - ); - - /// Map containing headers to be used on the request (e.g. Authorization). - final Map? headers; - - @override - List get props => [headers]; - - /// Merges this with the received [options] returning a new instance - ApiRequestOptions merge(ApiRequestOptions options) { - final newHeaders = options.headers != null || headers != null - ? { - ...headers ?? {}, - ...options.headers ?? {}, - } - : null; - - return ApiRequestOptions(headers: newHeaders); - } -} - -/// {@template http_api_client} -/// A client that prepares an http client based on an api key. -/// {@endtemplate} -class HttpApiClient { - /// {@macro http_api_client} - HttpApiClient({ - required String apiUrl, - required String apiKey, - required TokenProvider tokenProvider, - Client? httpClient, - }) : assert(!apiUrl.endsWith(_separator), () { - throw Exception('Error: api URL must not end with $_separator.'); - }), - _apiUrl = apiUrl, - _apiKey = apiKey, - _tokenProvider = tokenProvider, - _httpClient = httpClient ?? Client(); - - static const _separator = '/'; - - /// The base url of the API - final String _apiUrl; - - /// The API key used to access the http client - // ignore: unused_field - final String _apiKey; - - /// The provider of tokens for authenticated requests - final TokenProvider _tokenProvider; - - final Client _httpClient; - - ApiRequestOptions get _apiKeyHeader { - return const ApiRequestOptions( - headers: { - 'Accept': '*/*', - }, - ); - } - - Uri _buildUri(String path) { - assert(path.startsWith('/'), () { - throw Exception('Error: path must start with $_separator.'); - }); - return Uri.parse('$_apiUrl$path'); - } - - Map? _mapRequestOptions(ApiRequestOptions? options) { - if (options != null) { - return options.merge(_apiKeyHeader).headers; - } - return _apiKeyHeader.headers; - } - - Future _applyBearer(ApiRequestOptions? options) async { - final bearerToken = await _tokenProvider.fetchToken(); - if (bearerToken == null) { - throw const HttpException( - 'Attempt to make an authenticated request without a proper session', - ); - } - return ApiRequestOptions( - headers: { - 'Authorization': 'Bearer $bearerToken', - }, - ).merge(options ?? const ApiRequestOptions()); - } - - /// Similar to [post], but with applied authorization headers from the - /// session. - Future authenticatedPost( - String path, { - Object? body, - ApiRequestOptions? options, - }) async { - final authenticatedOptions = await _applyBearer(options); - return post( - path, - body: body, - options: authenticatedOptions, - ); - } - - /// Makes a post request to the given [path] and returns a future [Response]. - Future post( - String path, { - Object? body, - ApiRequestOptions? options, - }) { - return _httpClient.post( - _buildUri(path), - body: body, - headers: _mapRequestOptions(options), - ); - } - - /// Similar to [get], but with applied authorization headers from the session - Future authenticatedGet( - String path, { - ApiRequestOptions? options, - }) async { - final authenticatedOptions = await _applyBearer(options); - return get(path, options: authenticatedOptions); - } - - /// Makes a get request to the given [path] and returns a future [Response]. - Future get(String path, {ApiRequestOptions? options}) { - return _httpClient.get( - _buildUri(path), - headers: _mapRequestOptions(options), - ); - } - - /// Similar to [delete], but with applied authorization headers - /// from the session. - Future authenticatedDelete( - String path, { - Object? body, - ApiRequestOptions? options, - }) async { - final authenticatedOptions = await _applyBearer(options); - return delete(path, body: body, options: authenticatedOptions); - } - - /// Makes a delete request to the given [path] and returns a - /// future [Response]. - Future delete( - String path, { - Object? body, - ApiRequestOptions? options, - }) { - return _httpClient.delete( - _buildUri(path), - body: body, - headers: _mapRequestOptions(options), - ); - } - - /// Similar to [put], but with applied authorization headers from the session. - Future authenticatedPut( - String path, { - Object? body, - ApiRequestOptions? options, - }) async { - final authenticatedOptions = await _applyBearer(options); - return put( - path, - body: body, - options: authenticatedOptions, - ); - } - - /// Makes a put request to the given [path] and returns - /// a future [Response] - Future put( - String path, { - Object? body, - ApiRequestOptions? options, - }) { - return _httpClient.put( - _buildUri(path), - body: body, - headers: _mapRequestOptions(options), - ); - } - - /// Similar to [patch], but with applied authorization headers from the - /// session. - Future authenticatedPatch( - String path, { - Object? body, - ApiRequestOptions? options, - }) async { - final authenticatedOptions = await _applyBearer(options); - return patch( - path, - body: body, - options: authenticatedOptions, - ); - } - - /// Makes a patch request to the given [path] and returns - /// a future [Response] - Future patch( - String path, { - Object? body, - ApiRequestOptions? options, - }) { - return _httpClient.patch( - _buildUri(path), - body: body, - headers: _mapRequestOptions(options), - ); - } -} diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config.dart deleted file mode 100644 index 99ae036..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/app_config.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'app_config_resource.dart'; -export 'exceptions/exceptions.dart'; -export 'models/models.dart'; diff --git a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart b/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart deleted file mode 100644 index 3c28b9f..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/app_config_resource.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:convert'; - -import 'package:api_client/api_client.dart'; - -/// {@template app_config_resource} -/// A client for the app config resource. -/// {@endtemplate} -class AppConfigResource { - /// {@macro app_config_resource} - AppConfigResource({required HttpApiClient client}) : _client = client; - - final HttpApiClient _client; - - /// The base path for the app config resource. - static const String kUpgrade = 'app_config/upgrade'; - - /// The base path for the app config resource. - static const String kDownForMaintenance = 'app_config/down_for_maintenance'; - - /// Checks if the application is down for maintenance - Future getDownForMaintenance() async { - try { - final response = await _client.get(kDownForMaintenance); - if (response.statusCode == 200) { - final downForMaintenanceString = jsonDecode(response.body); - return downForMaintenanceString as bool; - } else { - throw CheckDownForMaintenanceFailure( - Exception( - 'Failed to check down for maintenance: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw CheckDownForMaintenanceFailure(error, stackTrace); - } - } - - /// Checks if force upgrade is required - Future getUpgrade() async { - try { - final response = await _client.get(kUpgrade); - if (response.statusCode == 200) { - final upgradeString = jsonDecode(response.body); - return Upgrade.fromJson( - Map.from(upgradeString as Map),); - } else { - throw CheckForceUpgradeFailure( - Exception( - 'Failed to check force upgrade: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw CheckForceUpgradeFailure(error, stackTrace); - } - } -} diff --git a/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart b/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart deleted file mode 100644 index 7ce3207..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/exceptions/exceptions.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:api_client/api_client.dart'; - -/// {@template app_config_exception} -/// Base class for all [AppConfigResource] exceptions. -/// {@endtemplate} -abstract class AppConfigException implements Exception { - /// {@macro app_config_exception} - const AppConfigException(this.error, this.stackTrace); - - /// The error which was caught. - final Object error; - - /// The error stack trace. - final StackTrace stackTrace; -} - -/// {@template check_force_upgrade_failure} -/// Thrown during the check force upgrade process if a failure occurs. -/// {@endtemplate} -class CheckForceUpgradeFailure extends UserException { - /// {@macro check_force_upgrade_failure} - const CheckForceUpgradeFailure(super.error, super.stackTrace); -} - -/// {@template check_down_for_maintenance_failure} -/// Thrown during the check down for maintenance process if a failure occurs. -/// {@endtemplate} -class CheckDownForMaintenanceFailure extends UserException { - /// {@macro check_down_for_maintenance_failure} - const CheckDownForMaintenanceFailure(super.error, super.stackTrace); -} diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/models.dart b/todo/packages/api_client/lib/src/resources/app_config/models/models.dart deleted file mode 100644 index 4d14450..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/models/models.dart +++ /dev/null @@ -1 +0,0 @@ -export 'upgrade.dart'; diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart deleted file mode 100644 index aba46b6..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'upgrade.g.dart'; - -/// {@template upgrade} -/// Model used to encapsulate configuration information -/// regarding a upgrade. -/// {@endtemplate} -@JsonSerializable() -class Upgrade extends Equatable { - /// {@macro force_upgrade} - const Upgrade({ - required this.androidUpgradeUrl, - required this.iosUpgradeUrl, - required this.androidBuildNumber, - required this.iosBuildNumber, - }); - - /// Deserializes the given [Map] into a [Upgrade]. - factory Upgrade.fromJson(Map json) => - _$UpgradeFromJson(json); - - /// The url where users can upgrade the android application. - final String androidUpgradeUrl; - - /// The url where users can upgrade the ios application. - final String iosUpgradeUrl; - - /// The latest android build number. - final int androidBuildNumber; - - /// The latest ios build number. - final int iosBuildNumber; - - /// Serializes the [Upgrade] into a [Map]. - Map toJson() => _$UpgradeToJson(this); - - @override - List get props => [ - androidUpgradeUrl, - iosUpgradeUrl, - androidBuildNumber, - iosBuildNumber, - ]; -} diff --git a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart b/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart deleted file mode 100644 index f7fb2f8..0000000 --- a/todo/packages/api_client/lib/src/resources/app_config/models/upgrade.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'upgrade.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Upgrade _$UpgradeFromJson(Map json) => Upgrade( - androidUpgradeUrl: json['androidUpgradeUrl'] as String, - iosUpgradeUrl: json['iosUpgradeUrl'] as String, - androidBuildNumber: (json['androidBuildNumber'] as num).toInt(), - iosBuildNumber: (json['iosBuildNumber'] as num).toInt(), - ); - -Map _$UpgradeToJson(Upgrade instance) => { - 'androidUpgradeUrl': instance.androidUpgradeUrl, - 'iosUpgradeUrl': instance.iosUpgradeUrl, - 'androidBuildNumber': instance.androidBuildNumber, - 'iosBuildNumber': instance.iosBuildNumber, - }; diff --git a/todo/packages/api_client/lib/src/resources/resources.dart b/todo/packages/api_client/lib/src/resources/resources.dart deleted file mode 100644 index 36432ee..0000000 --- a/todo/packages/api_client/lib/src/resources/resources.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'app_config/app_config.dart'; -export 'user/user.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart b/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart deleted file mode 100644 index 19d1b55..0000000 --- a/todo/packages/api_client/lib/src/resources/user/exceptions/exceptions.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:api_client/api_client.dart'; - -/// {@template user_exception} -/// Base class for all [UserResource] exceptions. -/// {@endtemplate} -abstract class UserException implements Exception { - /// {@macro user_exception} - const UserException(this.error, this.stackTrace); - - /// The error which was caught. - final Object error; - - /// The error stack trace. - final StackTrace stackTrace; -} - -/// {@template sign_up_failure} -/// Thrown during the sign up and create user process if a failure occurs. -/// {@endtemplate} -class SignUpFailure extends UserException { - /// {@macro sign_up_failure} - const SignUpFailure(super.error, super.stackTrace); -} - -/// {@template email_already_exist_failure} -/// Thrown during the sign up and create user process if a failure occurs. -/// {@endtemplate} -class EmailAlreadyExistFailure extends UserException { - /// {@macro email_already_exist_failure} - const EmailAlreadyExistFailure(super.error, super.stackTrace); -} - -/// {@template email_verification_failure} -/// Thrown during the email verification process if a failure occurs. -/// {@endtemplate} -class EmailVerificationFailure extends UserException { - /// {@macro email_verification_failure} - const EmailVerificationFailure(super.error, super.stackTrace); -} - -/// {@template forgot_password_failure} -/// Thrown during the forgot password process if a failure occurs. -/// {@endtemplate} -class ForgotPasswordFailure extends UserException { - /// {@macro forgot_password_failure} - const ForgotPasswordFailure(super.error, super.stackTrace); -} - -/// {@template update_password_failure} -/// Thrown during the update password process if a failure occurs. -/// {@endtemplate} -class UpdatePasswordFailure extends UserException { - /// {@macro update_password_failure} - const UpdatePasswordFailure(super.error, super.stackTrace); -} - -/// {@template send_otp_code_failure} -/// Thrown during the send otp code process if a failure occurs. -/// {@endtemplate} -class SendOtpCodeFailure extends UserException { - /// {@macro send_otp_code_failure} - const SendOtpCodeFailure(super.error, super.stackTrace); -} - -/// {@template get_authenticated_user_failure} -/// Thrown during the get authenticated user process if a failure occurs. -/// {@endtemplate} -class GetAuthenticatedUserFailure extends UserException { - /// {@macro get_authenticated_user_failure} - const GetAuthenticatedUserFailure(super.error, super.stackTrace); -} diff --git a/todo/packages/api_client/lib/src/resources/user/models/models.dart b/todo/packages/api_client/lib/src/resources/user/models/models.dart deleted file mode 100644 index 157c17b..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/models.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'sign_up_request.dart'; -export 'update_password_request.dart'; -export 'user_data.dart'; -export 'verification_request.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart deleted file mode 100644 index a0c7ff4..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'sign_up_request.g.dart'; - -/// {@template sign_up_request} -/// The request object for signing up a new user. -/// {@endtemplate} -@JsonSerializable() -class SignUpRequest extends Equatable { - /// {@macro sign_up_request} - const SignUpRequest({ - required this.email, - required this.password, - required this.name, - }); - - /// {@macro user} - factory SignUpRequest.fromJson(Map json) { - return _$SignUpRequestFromJson(json); - } - - /// The user's email. - final String email; - - /// The user's password. - final String password; - - /// The user's name. - final String name; - - @override - List get props => [email, password, name]; - - /// Converts the request to a JSON object. - Map toJson() { - return _$SignUpRequestToJson(this); - } -} diff --git a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart deleted file mode 100644 index e3dfbae..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/sign_up_request.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'sign_up_request.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SignUpRequest _$SignUpRequestFromJson(Map json) => - SignUpRequest( - email: json['email'] as String, - password: json['password'] as String, - name: json['name'] as String, - ); - -Map _$SignUpRequestToJson(SignUpRequest instance) => - { - 'email': instance.email, - 'password': instance.password, - 'name': instance.name, - }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart deleted file mode 100644 index 3ecc179..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'update_password_request.g.dart'; - -/// {@template update_password_request} -/// The request object for updating a user's password. -/// {@endtemplate} -@JsonSerializable() -class UpdatePasswordRequest extends Equatable { - /// {@macro update_password_request} - const UpdatePasswordRequest({ - required this.email, - required this.password, - required this.confirmationCode, - }); - - /// {@macro user} - factory UpdatePasswordRequest.fromJson(Map json) { - return _$UpdatePasswordRequestFromJson(json); - } - - /// The user's email. - final String email; - - /// The user's password. - final String password; - - /// The confirmation code. - final String confirmationCode; - - @override - List get props => [email, password, confirmationCode]; - - /// Converts the request to a JSON object. - Map toJson() { - return _$UpdatePasswordRequestToJson(this); - } -} diff --git a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart deleted file mode 100644 index 70c38f0..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/update_password_request.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'update_password_request.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UpdatePasswordRequest _$UpdatePasswordRequestFromJson( - Map json) => - UpdatePasswordRequest( - email: json['email'] as String, - password: json['password'] as String, - confirmationCode: json['confirmationCode'] as String, - ); - -Map _$UpdatePasswordRequestToJson( - UpdatePasswordRequest instance) => - { - 'email': instance.email, - 'password': instance.password, - 'confirmationCode': instance.confirmationCode, - }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/user_data.dart b/todo/packages/api_client/lib/src/resources/user/models/user_data.dart deleted file mode 100644 index c2ab789..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/user_data.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'user_data.g.dart'; - -/// {@template user_data} -/// A model representing a todo user. -/// {@endtemplate} -@JsonSerializable() -class UserData extends Equatable { - /// {@macro user_data} - const UserData({ - required this.id, - required this.email, - required this.name, - }); - - /// {@macro user_data} - factory UserData.fromJson(Map json) { - return _$UserDataFromJson(json); - } - - /// The user unique identifer. - final String id; - - /// The user email address. - final String email; - - /// The user name. - final String name; - - @override - List get props => [ - id, - email, - name, - ]; - - /// Returns a json built from this user. - Map toJson() { - return _$UserDataToJson(this); - } -} diff --git a/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart b/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart deleted file mode 100644 index 0d998c5..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/user_data.g.dart +++ /dev/null @@ -1,19 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_data.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserData _$UserDataFromJson(Map json) => UserData( - id: json['id'] as String, - email: json['email'] as String, - name: json['name'] as String, - ); - -Map _$UserDataToJson(UserData instance) => { - 'id': instance.id, - 'email': instance.email, - 'name': instance.name, - }; diff --git a/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart b/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart deleted file mode 100644 index 4b6e568..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/verification_request.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'verification_request.g.dart'; - -/// {@template email_verification_request} -/// The request object for email verification. -/// {@endtemplate} -@JsonSerializable() -class EmailVerificationRequest extends Equatable { - /// {@macro email_verification_request} - const EmailVerificationRequest({ - required this.email, - required this.code, - }); - - /// {@macro user} - factory EmailVerificationRequest.fromJson(Map json) { - return _$EmailVerificationRequestFromJson(json); - } - - /// The user's email. - final String email; - - /// The otp code received by email. - final String code; - - @override - List get props => [email, code]; - - /// Converts the request to a JSON object. - Map toJson() { - return _$EmailVerificationRequestToJson(this); - } -} diff --git a/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart b/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart deleted file mode 100644 index b58341c..0000000 --- a/todo/packages/api_client/lib/src/resources/user/models/verification_request.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'verification_request.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -EmailVerificationRequest _$EmailVerificationRequestFromJson( - Map json) => - EmailVerificationRequest( - email: json['email'] as String, - code: json['code'] as String, - ); - -Map _$EmailVerificationRequestToJson( - EmailVerificationRequest instance) => - { - 'email': instance.email, - 'code': instance.code, - }; diff --git a/todo/packages/api_client/lib/src/resources/user/user.dart b/todo/packages/api_client/lib/src/resources/user/user.dart deleted file mode 100644 index 97901cb..0000000 --- a/todo/packages/api_client/lib/src/resources/user/user.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'exceptions/exceptions.dart'; -export 'models/models.dart'; -export 'user_resource.dart'; diff --git a/todo/packages/api_client/lib/src/resources/user/user_resource.dart b/todo/packages/api_client/lib/src/resources/user/user_resource.dart deleted file mode 100644 index 85beb43..0000000 --- a/todo/packages/api_client/lib/src/resources/user/user_resource.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:convert'; - -import 'package:api_client/api_client.dart'; - -/// {@template user_resource} -/// A client for the user resource. -/// {@endtemplate} -class UserResource { - /// {@macro user_resource} - UserResource({required HttpApiClient client}) : _client = client; - - final HttpApiClient _client; - - /// The API endpoint for user sign-up. - static const kSignUp = '/auth/sign-up'; - - /// The API endpoint for email verification. - static const kVerifyEmail = '/auth/verification'; - - /// The API endpoint for password recovery. - static const kPasswordRecovery = '/auth/password-recovery'; - - /// The API endpoint for password confirmation. - static const kPasswordConfirmation = '/auth/password-confirmation'; - - /// The API endpoint for resending email verification. - static const kCodeVerification = '/auth/resend-verification'; - - /// The API endpoint for user-related operations. - static const kUser = '/user'; - - /// Attempts to sign up a new user with the provided [request]. - /// Throws a [SignUpFailure] if an error occurs. - Future signUp(SignUpRequest request) async { - try { - final response = await _client.post( - kSignUp, - body: request.toJson(), - ); - if (response.statusCode == 201) { - return; - } else if (response.statusCode == 409) { - throw EmailAlreadyExistFailure( - 'Failed to sign up user: ${response.statusCode} ${response.body}', - StackTrace.current, - ); - } else { - final body = jsonDecode(response.body) as Map; - final message = body['message'] as String; - throw SignUpFailure( - 'Failed to sign up user: $message [${response.statusCode}]', - StackTrace.current, - ); - } - } on Exception { - rethrow; - } - } - - /// Attempts to verify an email with the provided [request]. - /// - /// Throws a [EmailVerificationFailure] if an error occurs. - Future verifyEmail(EmailVerificationRequest request) async { - try { - final response = await _client.post( - kVerifyEmail, - body: request.toJson(), - ); - if (response.statusCode != 201) { - throw EmailVerificationFailure( - Exception( - 'Failed to verify email: ${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw EmailVerificationFailure(error, stackTrace); - } - } - - /// Attempts to recover the password for the provided [email]. - /// - /// Throws a [ForgotPasswordFailure] if an error occurs. - Future forgotPassword(String email) async { - try { - final response = await _client.post( - kPasswordRecovery, - body: {'email': email}, - ); - if (response.statusCode != 201) { - throw ForgotPasswordFailure( - Exception( - 'Failed to recover password of email: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw ForgotPasswordFailure(error, stackTrace); - } - } - - /// Attempts to update the password with the provided [request]. - /// - /// Throws a [UpdatePasswordFailure] if an error occurs. - Future updatePassword(UpdatePasswordRequest request) async { - try { - final response = await _client.post( - kPasswordConfirmation, - body: request.toJson(), - ); - if (response.statusCode != 201) { - throw UpdatePasswordFailure( - Exception( - 'Failed to update password: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw UpdatePasswordFailure(error, stackTrace); - } - } - - /// Attempts to resend verification for the provided [email]. - /// - /// Throws a [SendOtpCodeFailure] if an error occurs. - Future sendOtpCode(String email) async { - try { - final response = await _client.post( - kCodeVerification, - body: {'email': email}, - ); - if (response.statusCode != 201) { - throw SendOtpCodeFailure( - Exception( - 'Failed to resend verification: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw SendOtpCodeFailure(error, stackTrace); - } - } - - /// Attempts return the authenticated user. - /// - /// Throws a [GetAuthenticatedUserFailure] if an error occurs. - Future getAuthenticatedUser() async { - try { - final response = await _client.authenticatedGet(kUser); - if (response.statusCode == 200) { - final userString = jsonDecode(response.body); - return UserData.fromJson(Map.from(userString as Map)); - } else { - throw GetAuthenticatedUserFailure( - Exception( - 'Failed to get user: ' - '${response.statusCode} ${response.body}', - ), - StackTrace.current, - ); - } - } catch (error, stackTrace) { - throw GetAuthenticatedUserFailure(error, stackTrace); - } - } -} diff --git a/todo/packages/api_client/lib/src/utils/utils.dart b/todo/packages/api_client/lib/src/utils/utils.dart deleted file mode 100644 index a8e8a6f..0000000 --- a/todo/packages/api_client/lib/src/utils/utils.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Utility class for the API client. -class Utils { - /// Converts a list of strings to a single string, separated by commas. - static String? tryToListStringToString(List? value) { - if (value == null) return null; - return value.map((e) => e).join(','); - } -} diff --git a/todo/packages/api_client/pubspec.yaml b/todo/packages/api_client/pubspec.yaml deleted file mode 100644 index 618e84e..0000000 --- a/todo/packages/api_client/pubspec.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: api_client -description: A client to communicate with todo backend -version: 0.1.0+1 -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dependencies: - auth_client: - path: ../auth_client - equatable: ^2.0.5 - http: ^1.2.2 - json_annotation: ^4.9.0 - token_provider: - path: ../token_provider - -dev_dependencies: - build_runner: ^2.4.13 - json_serializable: ^6.8.0 - mocktail: ^1.0.4 - test: ^1.25.8 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/api_client/test/src/api_client_test.dart b/todo/packages/api_client/test/src/api_client_test.dart deleted file mode 100644 index 60e3c9e..0000000 --- a/todo/packages/api_client/test/src/api_client_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; - -class MockHttpApiClient extends Mock implements HttpApiClient {} - -void main() { - group('ApiClient', () { - late HttpApiClient httpClient; - late ApiClient apiClient; - - setUp(() { - httpClient = MockHttpApiClient(); - apiClient = ApiClient(client: httpClient); - }); - - test('can be instantiated', () { - expect(apiClient, isNotNull); - }); - - test('userResource returns a valid UserResource instance', () { - final userResource = apiClient.userResource; - expect(userResource, isA()); - }); - - test('appConfigResource returns a valid AppConfigResource instance', () { - final appConfigResource = apiClient.appConfigResource; - expect(appConfigResource, isA()); - }); - }); -} diff --git a/todo/packages/api_client/test/src/http_api_client_test.dart b/todo/packages/api_client/test/src/http_api_client_test.dart deleted file mode 100644 index c3cb849..0000000 --- a/todo/packages/api_client/test/src/http_api_client_test.dart +++ /dev/null @@ -1,498 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:http/http.dart'; - -import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; -import 'package:token_provider/token_provider.dart'; - -class MockClient extends Mock implements Client {} - -class MockTokenProvider extends Mock implements TokenProvider {} - -void main() { - group('HttpApiClient', () { - const path = '/some-api'; - const apiUrl = 'https://api.com'; - const apiKey = 'myApiKey'; - const apiKeyHeaders = { - 'Accept': '*/*', - }; - - late Client client; - late TokenProvider tokenProvider; - late HttpApiClient subject; - - Uri buildUri(String path) { - return Uri.parse('$apiUrl$path'); - } - - setUp(() { - tokenProvider = MockTokenProvider(); - client = MockClient(); - - subject = HttpApiClient( - apiUrl: apiUrl, - apiKey: apiKey, - httpClient: client, - tokenProvider: tokenProvider, - ); - }); - - test('can be instantiated', () { - expect( - HttpApiClient( - apiUrl: apiUrl, - apiKey: apiKey, - tokenProvider: tokenProvider, - ), - isNotNull, - ); - }); - - test('throw AssertionError when apiUrl ends with /', () { - expect( - () => HttpApiClient( - apiUrl: '$apiUrl/', - apiKey: apiKey, - tokenProvider: tokenProvider, - ), - throwsA(isA()), - ); - }); - - test('throw AssertionError when path does not start with /', () { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'Bearer token', - ); - - expectLater( - () async => subject.get('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.authenticatedGet('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.delete('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.authenticatedDelete('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.post('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.authenticatedPost('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.put('path'), - throwsA(isA()), - ); - - expectLater( - () async => subject.authenticatedPut('path'), - throwsA(isA()), - ); - }); - - group('applies the api key and base url to requests', () { - final uri = buildUri(path); - final body = {'message': 'data'}; - - test('on a get request', () async { - when(() => client.get(uri, headers: apiKeyHeaders)).thenAnswer( - (_) async => Response('body', 200), - ); - - final response = await subject.get(path); - - verify( - () => client.get(uri, headers: apiKeyHeaders), - ).called(1); - expect(response.statusCode, 200); - expect(response.body, 'body'); - }); - - test('on a delete request', () async { - when(() => client.delete(uri, headers: apiKeyHeaders)).thenAnswer( - (_) async => Response('body', 200), - ); - - final response = await subject.delete(path); - - verify( - () => client.delete(uri, headers: apiKeyHeaders), - ).called(1); - expect(response.statusCode, 200); - expect(response.body, 'body'); - }); - - test('on a post request', () async { - when( - () => client.post( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).thenAnswer( - (_) async => Response('body', 200), - ); - - await subject.post(path, body: body); - - verify( - () => client.post( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).called(1); - }); - - test('on a put request', () async { - when( - () => client.put( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).thenAnswer( - (_) async => Response('body', 200), - ); - - await subject.put(path, body: body); - - verify( - () => client.put( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).called(1); - }); - - test('on a patch request', () async { - when( - () => client.patch( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).thenAnswer( - (_) async => Response('body', 200), - ); - - await subject.patch(path, body: body); - - verify( - () => client.patch( - uri, - headers: apiKeyHeaders, - body: body, - ), - ).called(1); - }); - }); - - group('applies the options to the request', () { - final uri = buildUri(path); - final options = const ApiRequestOptions( - headers: { - 'Authorization': 'Bearer token', - }, - ).merge(const ApiRequestOptions(headers: apiKeyHeaders)); - - test('on a get request', () async { - when( - () => client.get(uri, headers: options.headers), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.get(path, options: options); - - verify(() => client.get(uri, headers: options.headers)).called(1); - }); - - test('on a delete request', () async { - when( - () => client.delete(uri, headers: options.headers), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.delete(path, options: options); - - verify(() => client.delete(uri, headers: options.headers)).called(1); - }); - - test('on a post request', () async { - when( - () => client.post( - uri, - headers: options.headers, - body: {'message': 'data'}, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.post( - path, - options: options, - body: {'message': 'data'}, - ); - - verify( - () => client.post( - uri, - headers: options.headers, - body: {'message': 'data'}, - ), - ).called(1); - }); - - test('on a put request', () async { - when( - () => client.put( - uri, - headers: options.headers, - body: {'message': 'data'}, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.put( - path, - options: options, - body: {'message': 'data'}, - ); - - verify( - () => client.put( - uri, - headers: options.headers, - body: {'message': 'data'}, - ), - ).called(1); - }); - }); - - group('authenticated requests', () { - final uri = buildUri('/some-api'); - - test('adds the bearer to get request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.get( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedGet(path); - verify( - () => client.get( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - ), - ).called(1); - }); - - test('adds the bearer to delete request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.delete( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedDelete(path); - verify( - () => client.delete( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - ), - ).called(1); - }); - - test('adds the bearer to post request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.post( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedPost(path, body: {'body'}); - verify( - () => client.post( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).called(1); - }); - - test('adds the bearer to put request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.put( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedPut(path, body: {'body'}); - verify( - () => client.put( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).called(1); - }); - - test('adds the bearer to patch request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.patch( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedPatch(path, body: {'body'}); - verify( - () => client.patch( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - }, - body: {'body'}, - ), - ).called(1); - }); - - test('correctly merges options on a get request', () async { - when(() => tokenProvider.fetchToken()).thenAnswer( - (_) async => 'MyBearerToken', - ); - when( - () => client.get( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - 'message': 'data', - }, - ), - ).thenAnswer((_) async => Response('body', 200)); - - await subject.authenticatedGet( - path, - options: const ApiRequestOptions( - headers: { - 'message': 'data', - }, - ), - ); - verify( - () => client.get( - uri, - headers: { - 'Accept': '*/*', - 'Authorization': 'Bearer MyBearerToken', - 'message': 'data', - }, - ), - ).called(1); - }); - }); - - group('ApiOptions', () { - group('merge', () { - test('creates a new instance with the merged attributes', () { - const options1 = ApiRequestOptions( - headers: { - 'Content-Type': 'json', - }, - ); - const options2 = ApiRequestOptions( - headers: { - 'Authorization': 'Bearer token', - }, - ); - - expect( - options1.merge(options2), - equals( - const ApiRequestOptions( - headers: { - 'Content-Type': 'json', - 'Authorization': 'Bearer token', - }, - ), - ), - ); - }); - test('can handle null values', () { - const options1 = ApiRequestOptions(); - const options2 = ApiRequestOptions(); - - expect( - options1.merge(options2).headers, - isNull, - ); - }); - }); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart b/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart deleted file mode 100644 index baea00f..0000000 --- a/todo/packages/api_client/test/src/resources/app_config/app_config_resources_test.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:convert'; - -import 'package:api_client/api_client.dart'; -import 'package:http/http.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; - -class MockHttpApiClient extends Mock implements HttpApiClient {} - -void main() { - late HttpApiClient mockHttpApiClient; - late AppConfigResource appConfigResource; - - setUp(() { - mockHttpApiClient = MockHttpApiClient(); - appConfigResource = AppConfigResource(client: mockHttpApiClient); - }); - - group('AppConfigResource', () { - test('getDownForMaintenance returns true when API call succeeds', () async { - final response = Response(jsonEncode(true), 200); - when(() => mockHttpApiClient.get(AppConfigResource.kDownForMaintenance)) - .thenAnswer((_) async => response); - - final result = await appConfigResource.getDownForMaintenance(); - - expect(result, isTrue); - }); - - test('getDownForMaintenance throws CheckDownForMaintenanceFailure on error', - () async { - final response = Response('Internal Server Error', 500); - when(() => mockHttpApiClient.get(AppConfigResource.kDownForMaintenance)) - .thenAnswer((_) async => response); - - expect( - () async => appConfigResource.getDownForMaintenance(), - throwsA(isA()), - ); - }); - - test('getUpgrade returns Upgrade when API call succeeds', () async { - final upgradeJson = { - 'androidBuildNumber': 22, - 'androidUpgradeUrl': 'https://example.com/upgrade', - 'iosBuildNumber': 22, - 'iosUpgradeUrl': 'https://example.com/upgrade', - }; - final response = Response(jsonEncode(upgradeJson), 200); - when(() => mockHttpApiClient.get(AppConfigResource.kUpgrade)) - .thenAnswer((_) async => response); - - final result = await appConfigResource.getUpgrade(); - - expect(result, isA()); - expect(result.androidBuildNumber, 22); - expect(result.androidUpgradeUrl, 'https://example.com/upgrade'); - expect(result.iosBuildNumber, 22); - expect(result.iosUpgradeUrl, 'https://example.com/upgrade'); - }); - - test('getUpgrade throws CheckForceUpgradeFailure on error', () async { - final response = Response('Internal Server Error', 500); - when(() => mockHttpApiClient.get(AppConfigResource.kUpgrade)) - .thenAnswer((_) async => response); - - expect( - () async => appConfigResource.getUpgrade(), - throwsA(isA()), - ); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart b/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart deleted file mode 100644 index 42ffa61..0000000 --- a/todo/packages/api_client/test/src/resources/app_config/exceptions/exceptions_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -class TestAppConfigException extends AppConfigException { - const TestAppConfigException(super.error, super.stackTrace); -} - -void main() { - group('AppConfigException', () { - test('can be instantiated', () { - final error = Exception('test error'); - final stackTrace = StackTrace.current; - - final exception = TestAppConfigException(error, stackTrace); - - expect(exception, isA()); - expect(exception.error, error); - expect(exception.stackTrace, stackTrace); - }); - - test('CheckForceUpgradeFailure can be instantiated', () { - final error = Exception('force upgrade error'); - final stackTrace = StackTrace.current; - - final exception = CheckForceUpgradeFailure(error, stackTrace); - - expect(exception, isA()); - expect(exception.error, error); - expect(exception.stackTrace, stackTrace); - }); - - test('CheckDownForMaintenanceFailure can be instantiated', () { - final error = Exception('down for maintenance error'); - final stackTrace = StackTrace.current; - - final exception = CheckDownForMaintenanceFailure(error, stackTrace); - - expect(exception, isA()); - expect(exception.error, error); - expect(exception.stackTrace, stackTrace); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart b/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart deleted file mode 100644 index d553235..0000000 --- a/todo/packages/api_client/test/src/resources/app_config/models/upgrade_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('Upgrade', () { - const upgrade = Upgrade( - androidUpgradeUrl: 'https://example.com/android', - iosUpgradeUrl: 'https://example.com/ios', - androidBuildNumber: 100, - iosBuildNumber: 200, - ); - - const upgradeJson = { - 'androidUpgradeUrl': 'https://example.com/android', - 'iosUpgradeUrl': 'https://example.com/ios', - 'androidBuildNumber': 100, - 'iosBuildNumber': 200, - }; - - test('supports value equality', () { - expect(upgrade, Upgrade.fromJson(upgradeJson)); - }); - - test('props are correct', () { - expect( - upgrade.props, - [ - 'https://example.com/android', - 'https://example.com/ios', - 100, - 200, - ], - ); - }); - - test('can be serialized to JSON', () { - expect(upgrade.toJson(), upgradeJson); - }); - - test('can be deserialized from JSON', () { - expect(Upgrade.fromJson(upgradeJson), upgrade); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart b/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart deleted file mode 100644 index 969fce9..0000000 --- a/todo/packages/api_client/test/src/resources/user/exceptions/exceptions_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('SignInFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Invalid arguments'); - final stackTrace = StackTrace.current; - - final signInFailure = SignInFailure(error, stackTrace); - - expect(signInFailure.error, equals(error)); - expect(signInFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('InvalidUserFailure', () { - test('constructs correctly', () { - final error = StateError('User not found'); - final stackTrace = StackTrace.current; - - final invalidUserFailure = InvalidUserFailure(error, stackTrace); - - expect(invalidUserFailure.error, equals(error)); - expect(invalidUserFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('ReAuthenticateFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Could not reauthenticate'); - final stackTrace = StackTrace.current; - - final reauthenticateFailure = ReAuthenticateFailure(error, stackTrace); - - expect(reauthenticateFailure.error, equals(error)); - expect(reauthenticateFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('DeleteAccountFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Invalid arguments'); - final stackTrace = StackTrace.current; - - final deleteAccountFailure = DeleteAccountFailure(error, stackTrace); - - expect(deleteAccountFailure.error, equals(error)); - expect(deleteAccountFailure.stackTrace, equals(stackTrace)); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart b/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart deleted file mode 100644 index 94bd7ed..0000000 --- a/todo/packages/api_client/test/src/resources/user/models/sign_up_requested_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('SignUpRequest', () { - const email = 'test@example.com'; - const password = 'password'; - const name = 'John Doe'; - - test('constructs properly', () { - const request = SignUpRequest( - email: email, - password: password, - name: name, - ); - expect(request.email, email); - expect(request.password, password); - expect(request.name, name); - }); - - test('fromJson creates a valid instance', () { - final json = { - 'email': email, - 'password': password, - 'name': name, - }; - final request = SignUpRequest.fromJson(json); - expect(request.email, email); - expect(request.password, password); - expect(request.name, name); - }); - - test('toJson returns a valid JSON object', () { - const request = SignUpRequest( - email: email, - password: password, - name: name, - ); - final json = request.toJson(); - expect(json['email'], email); - expect(json['password'], password); - expect(json['name'], name); - }); - - test('props list contains email, password, and name', () { - const request = SignUpRequest( - email: email, - password: password, - name: name, - ); - expect(request.props, containsAll([email, password, name])); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart b/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart deleted file mode 100644 index f3b6575..0000000 --- a/todo/packages/api_client/test/src/resources/user/models/update_password_requested_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('UpdatePasswordRequest', () { - const email = 'test@example.com'; - const password = 'newPassword'; - const confirmationCode = '123456'; - - test('constructs properly', () { - const request = UpdatePasswordRequest( - email: email, - password: password, - confirmationCode: confirmationCode, - ); - expect(request.email, email); - expect(request.password, password); - expect(request.confirmationCode, confirmationCode); - }); - - test('fromJson creates a valid instance', () { - final json = { - 'email': email, - 'password': password, - 'confirmationCode': confirmationCode, - }; - final request = UpdatePasswordRequest.fromJson(json); - expect(request.email, email); - expect(request.password, password); - expect(request.confirmationCode, confirmationCode); - }); - - test('toJson returns a valid JSON object', () { - const request = UpdatePasswordRequest( - email: email, - password: password, - confirmationCode: confirmationCode, - ); - final json = request.toJson(); - expect(json['email'], email); - expect(json['password'], password); - expect(json['confirmationCode'], confirmationCode); - }); - - test('props list contains email, password, and confirmationCode', () { - const request = UpdatePasswordRequest( - email: email, - password: password, - confirmationCode: confirmationCode, - ); - expect(request.props, containsAll([email, password, confirmationCode])); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart b/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart deleted file mode 100644 index 0a7abbf..0000000 --- a/todo/packages/api_client/test/src/resources/user/models/user_data_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('UserData', () { - const id = '123'; - const email = 'test@example.com'; - const name = 'John Doe'; - - test('constructs properly', () { - const userData = UserData( - id: id, - email: email, - name: name, - ); - expect(userData.id, id); - expect(userData.email, email); - expect(userData.name, name); - }); - - test('fromJson creates a valid instance', () { - final json = { - 'id': id, - 'email': email, - 'name': name, - }; - final userData = UserData.fromJson(json); - expect(userData.id, id); - expect(userData.email, email); - expect(userData.name, name); - }); - - test('toJson returns a valid JSON object', () { - const userData = UserData( - id: id, - email: email, - name: name, - ); - final json = userData.toJson(); - expect(json['id'], id); - expect(json['email'], email); - expect(json['name'], name); - }); - - test('props list contains id, email, and name', () { - const userData = UserData( - id: id, - email: email, - name: name, - ); - expect(userData.props, containsAll([id, email, name])); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart b/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart deleted file mode 100644 index 20fd397..0000000 --- a/todo/packages/api_client/test/src/resources/user/models/verification_request_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('EmailVerificationRequest', () { - const email = 'test@example.com'; - const code = '123456'; - - test('constructs properly', () { - const request = EmailVerificationRequest(email: email, code: code); - expect(request.email, email); - expect(request.code, code); - }); - - test('fromJson creates a valid instance', () { - final json = { - 'email': email, - 'code': code, - }; - final request = EmailVerificationRequest.fromJson(json); - expect(request.email, email); - expect(request.code, code); - }); - - test('toJson returns a valid JSON object', () { - const request = EmailVerificationRequest(email: email, code: code); - final json = request.toJson(); - expect(json['email'], email); - expect(json['code'], code); - }); - - test('props list contains email and code', () { - const request = EmailVerificationRequest(email: email, code: code); - expect(request.props, containsAll([email, code])); - }); - }); -} diff --git a/todo/packages/api_client/test/src/resources/user/user_resources_test.dart b/todo/packages/api_client/test/src/resources/user/user_resources_test.dart deleted file mode 100644 index 5b9ec76..0000000 --- a/todo/packages/api_client/test/src/resources/user/user_resources_test.dart +++ /dev/null @@ -1,458 +0,0 @@ -import 'dart:convert'; - -import 'package:api_client/api_client.dart'; -import 'package:http/http.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; - -class MockHttpApiClient extends Mock implements HttpApiClient {} - -class MockUserResource extends Mock implements UserResource {} - -void main() { - group('UserResource', () { - late HttpApiClient client; - late UserResource subject; - - setUp(() { - client = MockHttpApiClient(); - subject = UserResource(client: client); - }); - - test('signs up user correctly', () async { - when( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 201))); - - await subject.signUp( - const SignUpRequest( - email: 'test@example.com', - password: 'password', - name: 'Test User', - ), - ); - - verify( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).called(1); - }); - - test('throws EmailAlreadyExistFailure on duplicate email during sign-up', - () async { - when( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 409))); - - expect( - () => subject.signUp( - const SignUpRequest( - email: 'test@example.com', - password: 'password', - name: 'Test User', - ), - ), - throwsA(isA()), - ); - - verify( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).called(1); - }); - - test('throws error on status != 201 and != 409 during sign-up', () async { - when( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).thenAnswer( - (_) async => Future.value( - Response('{"message": "This is a test message"}', 500), - ), - ); - - expect( - () => subject.signUp( - const SignUpRequest( - email: 'test@example.com', - password: 'password', - name: 'Test User', - ), - ), - throwsA(isA()), - ); - - verify( - () => client.post( - UserResource.kSignUp, - body: { - 'email': 'test@example.com', - 'password': 'password', - 'name': 'Test User', - }, - ), - ).called(1); - }); - - test('Verifies email successfully', () async { - when( - () => client.post( - UserResource.kVerifyEmail, - body: { - 'email': 'email', - 'code': 'code', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 201))); - - await subject.verifyEmail( - const EmailVerificationRequest( - email: 'email', - code: 'code', - ), - ); - - verify( - () => client.post( - UserResource.kVerifyEmail, - body: { - 'email': 'email', - 'code': 'code', - }, - ), - ).called(1); - }); - - test('Verifies email failure with response code != 201', () async { - when( - () => client.post( - UserResource.kVerifyEmail, - body: { - 'email': 'email', - 'code': 'code', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 200))); - - expect( - () => subject.verifyEmail( - const EmailVerificationRequest( - email: 'email', - code: 'code', - ), - ), - throwsA(isA()), - ); - }); - - test('Verifies email failure', () async { - when( - () => client.post( - UserResource.kVerifyEmail, - body: { - 'email': 'email', - 'code': 'code', - }, - ), - ).thenThrow(Exception()); - - expect( - () => subject.verifyEmail( - const EmailVerificationRequest( - email: 'email', - code: 'code', - ), - ), - throwsA(isA()), - ); - }); - - test('Recovers password successfully', () async { - when( - () => client.post( - UserResource.kPasswordRecovery, - body: { - 'email': 'email', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 201))); - - await subject.forgotPassword( - 'email', - ); - - verify( - () => client.post( - UserResource.kPasswordRecovery, - body: { - 'email': 'email', - }, - ), - ).called(1); - }); - - test('Recovers password failure with response code != 201', () async { - when( - () => client.post( - UserResource.kPasswordRecovery, - body: { - 'email': 'email', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 200))); - - expect( - () => subject.forgotPassword( - 'email', - ), - throwsA(isA()), - ); - }); - - test('Recovers password with error', () async { - when( - () => client.post( - UserResource.kPasswordRecovery, - body: { - 'email': 'email', - }, - ), - ).thenThrow(Exception()); - - expect( - () => subject.forgotPassword( - 'email', - ), - throwsA(isA()), - ); - }); - - test('Updates password successfully', () async { - when( - () => client.post( - UserResource.kPasswordConfirmation, - body: { - 'email': 'email', - 'password': 'password', - 'confirmationCode': 'confirmationCode', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 201))); - - await subject.updatePassword( - const UpdatePasswordRequest( - email: 'email', - password: 'password', - confirmationCode: 'confirmationCode', - ), - ); - - verify( - () => client.post( - UserResource.kPasswordConfirmation, - body: { - 'email': 'email', - 'password': 'password', - 'confirmationCode': 'confirmationCode', - }, - ), - ).called(1); - }); - - test('Updates password failure with response code != 201', () async { - when( - () => client.post( - UserResource.kPasswordConfirmation, - body: { - 'email': 'email', - 'password': 'password', - 'confirmationCode': 'confirmationCode', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 200))); - - expect( - () => subject.updatePassword( - const UpdatePasswordRequest( - email: 'email', - password: 'password', - confirmationCode: 'confirmationCode', - ), - ), - throwsA(isA()), - ); - }); - - test('Updates password with error', () async { - when( - () => client.post( - UserResource.kPasswordConfirmation, - body: { - 'email': 'email', - 'password': 'password', - 'confirmation_code': 'confirmationCode', - }, - ), - ).thenThrow(Exception()); - - expect( - () => subject.updatePassword( - const UpdatePasswordRequest( - email: 'email', - password: 'password', - confirmationCode: 'confirmationCode', - ), - ), - throwsA(isA()), - ); - }); - - test('Sends OTP successfully', () async { - when( - () => client.post( - UserResource.kCodeVerification, - body: { - 'email': 'email', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 201))); - - await subject.sendOtpCode( - 'email', - ); - - verify( - () => client.post( - UserResource.kCodeVerification, - body: { - 'email': 'email', - }, - ), - ).called(1); - }); - - test('Sends OTP failure with response code != 201', () async { - when( - () => client.post( - UserResource.kCodeVerification, - body: { - 'email': 'email', - }, - ), - ).thenAnswer((_) async => Future.value(Response('', 200))); - - expect( - () => subject.sendOtpCode( - 'email', - ), - throwsA(isA()), - ); - }); - - test('Sends OTP with error', () async { - when( - () => client.post( - UserResource.kCodeVerification, - body: { - 'email': 'email', - }, - ), - ).thenThrow(Exception()); - - expect( - () => subject.sendOtpCode( - 'email', - ), - throwsA(isA()), - ); - }); - - test('Gets authenticated user successfully', () async { - when( - () => client.authenticatedGet(UserResource.kUser), - ).thenAnswer( - (_) async => Future.value( - Response( - json.encode({ - 'id': 'id', - 'email': 'email', - 'name': 'name', - }), - 200, - ), - ), - ); - - await subject.getAuthenticatedUser(); - - verify( - () => client.authenticatedGet(UserResource.kUser), - ).called(1); - }); - - test('Gets authenticated user failure with response code != 200', () async { - when( - () => client.authenticatedGet(UserResource.kUser), - ).thenAnswer( - (_) async => Future.value( - Response( - json.encode({ - 'id': 'id', - 'email': 'email', - 'name': 'name', - }), - 400, - ), - ), - ); - - expect( - () => subject.getAuthenticatedUser(), - throwsA(isA()), - ); - }); - - test('Gets authenticated user with error', () async { - when( - () => client.post(UserResource.kUser), - ).thenThrow(Exception()); - - expect( - () => subject.getAuthenticatedUser(), - throwsA(isA()), - ); - }); - }); -} diff --git a/todo/packages/api_client/test/src/utils/utils_test.dart b/todo/packages/api_client/test/src/utils/utils_test.dart deleted file mode 100644 index fec3dbe..0000000 --- a/todo/packages/api_client/test/src/utils/utils_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('Utils', () { - test('tryToListStringToString returns null for null input', () { - expect(Utils.tryToListStringToString(null), isNull); - }); - - test('tryToListStringToString returns string separated by commas', () { - final input = ['a', 'b', 'c']; - const expected = 'a,b,c'; - expect(Utils.tryToListStringToString(input), equals(expected)); - }); - - test('tryToListStringToString returns empty string for empty list', () { - final input = []; - const expected = ''; - expect(Utils.tryToListStringToString(input), equals(expected)); - }); - }); -} diff --git a/todo/packages/app_config_repository/analysis_options.yaml b/todo/packages/app_config_repository/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/app_config_repository/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/app_config_repository/build.yaml b/todo/packages/app_config_repository/build.yaml deleted file mode 100644 index 917f456..0000000 --- a/todo/packages/app_config_repository/build.yaml +++ /dev/null @@ -1,12 +0,0 @@ -targets: - $default: - builders: - source_gen|combining_builder: - options: - ignore_for_file: - - implicit_dynamic_parameter - json_serializable: - options: - field_rename: snake - create_to_json: false - checked: true diff --git a/todo/packages/app_config_repository/lib/app_config_repository.dart b/todo/packages/app_config_repository/lib/app_config_repository.dart deleted file mode 100644 index ce33380..0000000 --- a/todo/packages/app_config_repository/lib/app_config_repository.dart +++ /dev/null @@ -1,5 +0,0 @@ -/// An App Config implementation -library app_config_repository; - -export 'src/app_config_repository.dart'; -export 'src/models/models.dart' hide AppConfig; diff --git a/todo/packages/app_config_repository/lib/src/app_config_repository.dart b/todo/packages/app_config_repository/lib/src/app_config_repository.dart deleted file mode 100644 index 426f271..0000000 --- a/todo/packages/app_config_repository/lib/src/app_config_repository.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'dart:async'; -import 'package:api_client/api_client.dart'; -import 'package:app_config_repository/src/models/models.dart'; - -/// {@template app_config_repository} -/// Repository which manages determining whether the application -/// is in maintenance mode or needs to be force upgraded. -/// {@endtemplate} -class AppConfigRepository { - /// {@macro app_config_repository} - AppConfigRepository({ - required int buildNumber, - required Platform platform, - required ApiClient apiClient, - }) : assert(buildNumber > 0, 'The build number must be greater than 0.'), - _buildNumber = buildNumber, - _platform = platform, - _apiClient = apiClient; - - final ApiClient _apiClient; - final int _buildNumber; - final Platform _platform; - - /// Returns a [Stream] which indicates whether - /// the current application status is down for maintenance. - /// - /// By default, [isDownForMaintenance] will emit `false` - /// if unable to connected to the backend. - Stream isDownForMaintenance() { - return _apiClient.appConfigResource.getDownForMaintenance().asStream(); - } - - /// Returns a [Stream] which indicates whether - /// the current application requires a force upgrade. - Stream isForceUpgradeRequired() { - return _apiClient.appConfigResource.getUpgrade().asStream().map((upgrade) { - bool isUpgradeRequired; - String upgradeUrl; - - if (_platform == Platform.android) { - isUpgradeRequired = _buildNumber < upgrade.androidBuildNumber; - upgradeUrl = upgrade.androidUpgradeUrl; - } else { - isUpgradeRequired = _buildNumber < upgrade.iosBuildNumber; - upgradeUrl = upgrade.iosUpgradeUrl; - } - - return ForceUpgrade( - isUpgradeRequired: isUpgradeRequired, - upgradeUrl: isUpgradeRequired ? upgradeUrl : '', - ); - }); - } -} diff --git a/todo/packages/app_config_repository/lib/src/models/app_config.dart b/todo/packages/app_config_repository/lib/src/models/app_config.dart deleted file mode 100644 index 294b76b..0000000 --- a/todo/packages/app_config_repository/lib/src/models/app_config.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'app_config.g.dart'; - -/// {@template app_config} -/// Application Configuration which contains the relevant -/// information needed to determine whether or not the application is -/// down for maintenance or requires a force upgrade. -/// {@endtemplate} -@JsonSerializable() -class AppConfig extends Equatable { - /// {@macro app_config} - const AppConfig({ - required this.androidUpgradeUrl, - required this.downForMaintenance, - required this.iosUpgradeUrl, - required this.minAndroidBuildNumber, - required this.minIosBuildNumber, - }); - - /// Converts a [Map] into an [AppConfig] instance. - factory AppConfig.fromJson(Map json) => - _$AppConfigFromJson(json); - - /// Whether the app is down for maintenance. - final bool downForMaintenance; - - /// The minimum supported build number for Android. - final int minAndroidBuildNumber; - - /// The minimum supported build number for iOS. - final int minIosBuildNumber; - - /// The upgrade URL for Android. - final String androidUpgradeUrl; - - /// The upgrade URL for iOS. - final String iosUpgradeUrl; - - @override - List get props => [ - androidUpgradeUrl, - downForMaintenance, - iosUpgradeUrl, - minAndroidBuildNumber, - minIosBuildNumber, - ]; -} diff --git a/todo/packages/app_config_repository/lib/src/models/app_config.g.dart b/todo/packages/app_config_repository/lib/src/models/app_config.g.dart deleted file mode 100644 index 797b921..0000000 --- a/todo/packages/app_config_repository/lib/src/models/app_config.g.dart +++ /dev/null @@ -1,33 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ignore_for_file: implicit_dynamic_parameter - -part of 'app_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AppConfig _$AppConfigFromJson(Map json) { - return $checkedNew('AppConfig', json, () { - final val = AppConfig( - androidUpgradeUrl: - $checkedConvert(json, 'android_upgrade_url', (v) => v as String), - downForMaintenance: - $checkedConvert(json, 'down_for_maintenance', (v) => v as bool), - iosUpgradeUrl: - $checkedConvert(json, 'ios_upgrade_url', (v) => v as String), - minAndroidBuildNumber: - $checkedConvert(json, 'min_android_build_number', (v) => v as int), - minIosBuildNumber: - $checkedConvert(json, 'min_ios_build_number', (v) => v as int), - ); - return val; - }, fieldKeyMap: const { - 'androidUpgradeUrl': 'android_upgrade_url', - 'downForMaintenance': 'down_for_maintenance', - 'iosUpgradeUrl': 'ios_upgrade_url', - 'minAndroidBuildNumber': 'min_android_build_number', - 'minIosBuildNumber': 'min_ios_build_number' - }); -} diff --git a/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart b/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart deleted file mode 100644 index 2074961..0000000 --- a/todo/packages/app_config_repository/lib/src/models/force_upgrade.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:equatable/equatable.dart'; - -/// Platform which is used to determine whether an upgrade is required. -enum Platform { - /// Google's OS for mobile and tablet. - android, - - /// Apple's OS for iPhone and iPad. - iOS; - - /// Whether the current platform is [Platform.android]. - bool get isAndroid => this == Platform.android; - - /// Whether the current platform is [Platform.iOS]. - bool get isIos => this == Platform.iOS; -} - -/// {@template force_upgrade} -/// Model used to encapsulate configuration information -/// regarding force upgrade. -/// -/// Includes a `bool` [isUpgradeRequired] which determines if -/// the current application requires a force upgrade as well as -/// an [upgradeUrl] to link to the latest version of the application. -/// {@endtemplate} -class ForceUpgrade extends Equatable { - /// {@macro force_upgrade} - const ForceUpgrade({ - required this.isUpgradeRequired, - this.upgradeUrl, - }); - - /// Whether an upgrade is required. - final bool isUpgradeRequired; - - /// The url where users can upgrade the application. - final String? upgradeUrl; - - @override - List get props => [isUpgradeRequired, upgradeUrl]; -} diff --git a/todo/packages/app_config_repository/lib/src/models/models.dart b/todo/packages/app_config_repository/lib/src/models/models.dart deleted file mode 100644 index 88bfe03..0000000 --- a/todo/packages/app_config_repository/lib/src/models/models.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'app_config.dart'; -export 'force_upgrade.dart'; diff --git a/todo/packages/app_config_repository/pubspec.yaml b/todo/packages/app_config_repository/pubspec.yaml deleted file mode 100644 index 8b3f714..0000000 --- a/todo/packages/app_config_repository/pubspec.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: app_config_repository -description: Dart package which manages the app configuration domain. -version: 0.1.0+1 -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dependencies: - api_client: - path: ../api_client - equatable: ^2.0.5 - json_annotation: ^4.9.0 - -dev_dependencies: - build_runner: ^2.4.13 - flutter_test: - sdk: flutter - json_serializable: ^6.8.0 - mocktail: ^1.0.4 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/app_config_repository/test/app_config_repository_test.dart b/todo/packages/app_config_repository/test/app_config_repository_test.dart deleted file mode 100644 index 64d310e..0000000 --- a/todo/packages/app_config_repository/test/app_config_repository_test.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockApiClient extends Mock implements ApiClient {} - -class MockAppConfigResource extends Mock implements AppConfigResource {} - -void main() { - late AppConfigRepository repository; - late AppConfigRepository repositoryForiOS; - late ApiClient mockApiClient; - late AppConfigResource configResource; - - setUp(() { - mockApiClient = MockApiClient(); - configResource = MockAppConfigResource(); - repository = AppConfigRepository( - buildNumber: 10, - platform: Platform.android, - apiClient: mockApiClient, - ); - repositoryForiOS = AppConfigRepository( - buildNumber: 10, - platform: Platform.iOS, - apiClient: mockApiClient, - ); - }); - - group('AppConfigRepository', () { - test('isDownForMaintenance returns stream of booleans', () { - when(() => mockApiClient.appConfigResource).thenReturn(configResource); - when(() => configResource.getDownForMaintenance()).thenAnswer( - (_) => Future.value(false), - ); - - final stream = repository.isDownForMaintenance(); - - expect(stream, emits(false)); - }); - - test('isForceUpgradeRequired handles iOS platform', () { - const mockUpgrade = Upgrade( - androidBuildNumber: 20, - iosBuildNumber: 20, - androidUpgradeUrl: 'https://example.com/android', - iosUpgradeUrl: 'https://example.com/ios', - ); - - const mockForceUpgrade = ForceUpgrade( - isUpgradeRequired: false, - upgradeUrl: '', - ); - - final mockResponse = Future.value(mockUpgrade); - when(() => mockApiClient.appConfigResource).thenReturn(configResource); - when(() => configResource.getUpgrade()).thenAnswer((_) => mockResponse); - - final stream = repository.isForceUpgradeRequired(); - - expect(stream, emits(mockForceUpgrade)); - }); - - test('isForceUpgradeRequired handles iOS platform', () { - const mockUpgrade = Upgrade( - androidBuildNumber: 20, - iosBuildNumber: 20, - androidUpgradeUrl: 'https://example.com/android', - iosUpgradeUrl: 'https://example.com/ios', - ); - - const mockForceUpgrade = ForceUpgrade( - isUpgradeRequired: false, - upgradeUrl: '', - ); - - final mockResponse = Future.value(mockUpgrade); - when(() => mockApiClient.appConfigResource).thenReturn(configResource); - when(() => configResource.getUpgrade()).thenAnswer((_) => mockResponse); - - final stream = repositoryForiOS.isForceUpgradeRequired(); - - expect(stream, emits(mockForceUpgrade)); - }); - }); -} diff --git a/todo/packages/app_config_repository/test/models/app_config_test.dart b/todo/packages/app_config_repository/test/models/app_config_test.dart deleted file mode 100644 index 2047441..0000000 --- a/todo/packages/app_config_repository/test/models/app_config_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:app_config_repository/src/models/models.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('AppConfig', () { - test('equatable props', () { - const appConfig1 = AppConfig( - androidUpgradeUrl: 'https://example.com/android', - downForMaintenance: true, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 100, - minIosBuildNumber: 200, - ); - - const appConfig2 = AppConfig( - androidUpgradeUrl: 'https://example.com/android', - downForMaintenance: true, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 100, - minIosBuildNumber: 200, - ); - - const appConfig3 = AppConfig( - androidUpgradeUrl: 'https://different-url.com/android', - downForMaintenance: true, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 100, - minIosBuildNumber: 200, - ); - - expect(appConfig1, equals(appConfig2)); - expect(appConfig1 == appConfig3, isFalse); - }); - - test('fromJson', () { - final json = { - 'android_upgrade_url': 'https://example.com/android', - 'down_for_maintenance': true, - 'ios_upgrade_url': 'https://example.com/ios', - 'min_android_build_number': 100, - 'min_ios_build_number': 100, - }; - - final appConfig = AppConfig.fromJson(json); - - expect(appConfig.androidUpgradeUrl, equals(json['android_upgrade_url'])); - expect( - appConfig.downForMaintenance, - equals(json['down_for_maintenance']), - ); - expect(appConfig.iosUpgradeUrl, equals(json['ios_upgrade_url'])); - expect( - appConfig.minAndroidBuildNumber, - equals(json['min_android_build_number']), - ); - expect(appConfig.minIosBuildNumber, equals(json['min_ios_build_number'])); - }); - - test('supports value equality', () { - const appConfig = AppConfig( - androidUpgradeUrl: 'https://example.com/android', - downForMaintenance: true, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 100, - minIosBuildNumber: 200, - ); - const secondAppConfig = AppConfig( - androidUpgradeUrl: 'https://example.com/android', - downForMaintenance: true, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 100, - minIosBuildNumber: 200, - ); - const appConfig2 = AppConfig( - androidUpgradeUrl: 'https://different-url.com/android', - downForMaintenance: false, - iosUpgradeUrl: 'https://example.com/ios', - minAndroidBuildNumber: 200, - minIosBuildNumber: 200, - ); - - expect(appConfig, equals(secondAppConfig)); - expect(appConfig, isNot(equals(appConfig2))); - }); - }); -} diff --git a/todo/packages/app_config_repository/test/models/force_upgrade_test.dart b/todo/packages/app_config_repository/test/models/force_upgrade_test.dart deleted file mode 100644 index 7f8dd0c..0000000 --- a/todo/packages/app_config_repository/test/models/force_upgrade_test.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('Platform', () { - test('isAndroid should return true for Platform.android', () { - expect(Platform.android.isAndroid, isTrue); - expect(Platform.android.isIos, isFalse); - }); - - test('isIos should return true for Platform.iOS', () { - expect(Platform.iOS.isAndroid, isFalse); - expect(Platform.iOS.isIos, isTrue); - }); - - test('Platform equality', () { - expect(Platform.android, equals(Platform.android)); - expect(Platform.iOS, equals(Platform.iOS)); - expect(Platform.android == Platform.iOS, isFalse); - }); - }); - - group('ForceUpgrade', () { - test('equality', () { - const upgrade1 = ForceUpgrade( - isUpgradeRequired: true, - upgradeUrl: 'https://example.com/upgrade1', - ); - - const upgrade2 = ForceUpgrade( - isUpgradeRequired: true, - upgradeUrl: 'https://example.com/upgrade1', - ); - - const upgrade3 = ForceUpgrade( - isUpgradeRequired: true, - upgradeUrl: 'https://example.com/upgrade2', - ); - - expect(upgrade1, equals(upgrade2)); - expect(upgrade1 == upgrade3, isFalse); - }); - - test('props should include all fields', () { - const upgrade = ForceUpgrade( - isUpgradeRequired: true, - upgradeUrl: 'https://example.com/upgrade', - ); - - expect(upgrade.props, containsAll([true, 'https://example.com/upgrade'])); - }); - }); -} diff --git a/todo/packages/auth_client/.gitignore b/todo/packages/auth_client/.gitignore deleted file mode 100644 index ea1d8dc..0000000 --- a/todo/packages/auth_client/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.dart_tool/ -.packages -build/ -pubspec.lock \ No newline at end of file diff --git a/todo/packages/auth_client/analysis_options.yaml b/todo/packages/auth_client/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/auth_client/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/auth_client/lib/auth_client.dart b/todo/packages/auth_client/lib/auth_client.dart deleted file mode 100644 index 6d7c598..0000000 --- a/todo/packages/auth_client/lib/auth_client.dart +++ /dev/null @@ -1,6 +0,0 @@ -/// An authentication client implementation -library auth_client; - -export 'src/auth_client.dart'; -export 'src/exceptions/exceptions.dart'; -export 'src/models/models.dart'; diff --git a/todo/packages/auth_client/lib/src/auth_client.dart b/todo/packages/auth_client/lib/src/auth_client.dart deleted file mode 100644 index 79ca702..0000000 --- a/todo/packages/auth_client/lib/src/auth_client.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'dart:async'; - -import 'package:auth_client/auth_client.dart'; -import 'package:rxdart/subjects.dart'; -import 'package:token_provider/token_provider.dart'; - -/// {@template auth_client} -/// An authentication client implementation baked with [Auth Service] -/// {@endtemplate} -class AuthenticationClient implements TokenProvider { - /// {@macro auth_client} - AuthenticationClient() - : authEventSubject = BehaviorSubject() { - _initialize(); - } - - /// A [BehaviorSubject] that emits [AuthenticationEvent]s. - final BehaviorSubject authEventSubject; - - Future _initialize() async { - authEventSubject.add( - const AuthenticationEvent( - type: AuthEventType.signedOut, - ), - ); - } - - /// Emits a new event when the [AuthEventType] status changes. - Stream get onAuthStatusChanged { - return authEventSubject.stream; - } - - /// Initiates the sign in process with [email] and [password]. - /// - /// If an error occurs, it throws one of the following [Exception]s: - /// - [InvalidUserFailure] in case the user wasn't found - /// - [SignInFailure] in case of an unknown error - Future signInUser({ - required String email, - required String password, - }) async { - try { - const userId = 'userId'; - authEventSubject.add( - AuthenticationEvent( - type: AuthEventType.signedIn, - user: AuthenticationUser( - id: userId, - email: email, - ), - ), - ); - } catch (error) { - authEventSubject.add( - const AuthenticationEvent( - type: AuthEventType.signedOut, - ), - ); - } - } - - @override - Future fetchToken() async { - return 'token'; - } - - @override - Future requestTokenRefresh() { - throw UnimplementedError(); - } - - /// Signs out the current user. - Future signOut() async { - authEventSubject.add( - const AuthenticationEvent( - type: AuthEventType.signedOut, - ), - ); - } - - /// Re-authenticates the current user. - Future reAuthenticate({ - required String email, - required String password, - }) { - throw UnimplementedError(); - } - - /// Deletes the current user. - Future deleteAccount() async { - authEventSubject.add( - const AuthenticationEvent( - type: AuthEventType.userDeleted, - ), - ); - } -} diff --git a/todo/packages/auth_client/lib/src/exceptions/exceptions.dart b/todo/packages/auth_client/lib/src/exceptions/exceptions.dart deleted file mode 100644 index 976aca0..0000000 --- a/todo/packages/auth_client/lib/src/exceptions/exceptions.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:auth_client/auth_client.dart'; - -/// {@template authentication_client_exception} -/// Base class for all [AuthenticationClient] exceptions. -/// {@endtemplate} -abstract class AuthenticationClientException implements Exception { - /// {@macro authentication_client_exception} - const AuthenticationClientException( - this.error, - this.stackTrace, - ); - - /// The error which was caught. - final Object error; - - /// The error stack trace. - final StackTrace stackTrace; -} - -/// {@template sign_in_failure} -/// Thrown when an error occurs on the sign in process. -/// {@endtemplate} -class SignInFailure extends AuthenticationClientException { - /// {@macro sign_in_failure} - const SignInFailure( - super.error, - super.stackTrace, - ); -} - -/// {@template invalid_user_failure} -/// Thrown when an error occurs on the sign in process because the user -/// was not found. -/// {@endtemplate} -class InvalidUserFailure extends AuthenticationClientException { - /// {@macro invalid_user_failure} - const InvalidUserFailure( - super.error, - super.stackTrace, - ); -} - -/// {@template reauthenticate_failure} -/// Thrown when an error occurs on the reauthentication process. -/// {@endtemplate} -class ReAuthenticateFailure extends AuthenticationClientException { - /// {@macro reauthenticate_failure} - const ReAuthenticateFailure( - super.error, - super.stackTrace, - ); -} - -/// {@template delete_account_failure} -/// Thrown when an error occurs on the delete account process. -/// {@endtemplate} -class DeleteAccountFailure extends AuthenticationClientException { - /// {@macro delete_account_failure} - const DeleteAccountFailure( - super.error, - super.stackTrace, - ); -} diff --git a/todo/packages/auth_client/lib/src/models/authentication_event.dart b/todo/packages/auth_client/lib/src/models/authentication_event.dart deleted file mode 100644 index b988aa2..0000000 --- a/todo/packages/auth_client/lib/src/models/authentication_event.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:equatable/equatable.dart'; - -/// {@template auth_event_type} -/// Model representing the different event types regarding the authentication -/// status. -/// {@endtemplate} -enum AuthEventType { - /// Signed in event. - signedIn, - - /// Signed out event. - signedOut, - - /// Session expired event. - sessionExpired, - - /// User deleted event. - userDeleted; - - /// Whether `this` is equal to [signedIn]. - bool get isSignedIn => this == signedIn; - - /// Whether `this` is equal to [signedOut]. - bool get isSignedOut => this == signedOut; - - /// Whether `this` is equal to [sessionExpired]. - bool get isSessionExpired => this == sessionExpired; - - /// Whether `this` is equal to [userDeleted]. - bool get isUserDeleted => this == userDeleted; -} - -/// {@template authentication_event} -/// Model representing an authentication event. -/// {@endtemplate} -class AuthenticationEvent extends Equatable { - /// {@macro authentication_event} - const AuthenticationEvent({ - required this.type, - AuthenticationUser? user, - }) : user = user ?? AuthenticationUser.anonymous; - - /// The authentication user. - final AuthenticationUser user; - - /// The authentication event type. - final AuthEventType type; - - @override - List get props => [user, type]; -} diff --git a/todo/packages/auth_client/lib/src/models/authentication_user.dart b/todo/packages/auth_client/lib/src/models/authentication_user.dart deleted file mode 100644 index e2cf3f3..0000000 --- a/todo/packages/auth_client/lib/src/models/authentication_user.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:equatable/equatable.dart'; - -/// {@template authentication_user} -/// Model representing a User from the authentication domain. -/// {@endtemplate} -class AuthenticationUser extends Equatable { - /// {@macro authentication_user} - const AuthenticationUser({ - required this.id, - required this.email, - }); - - /// An anonymous user representing the signed out state. - static const anonymous = AuthenticationUser( - id: '', - email: '', - ); - - /// Authentication user id. - final String id; - - /// Authentication user email address. - final String email; - - @override - List get props => [id, email]; - - /// Whether `this` is equal to [anonymous]. - bool get isAnonymous => this == anonymous; -} diff --git a/todo/packages/auth_client/lib/src/models/models.dart b/todo/packages/auth_client/lib/src/models/models.dart deleted file mode 100644 index fc06f78..0000000 --- a/todo/packages/auth_client/lib/src/models/models.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'authentication_event.dart'; -export 'authentication_user.dart'; diff --git a/todo/packages/auth_client/pubspec.yaml b/todo/packages/auth_client/pubspec.yaml deleted file mode 100644 index 3e45917..0000000 --- a/todo/packages/auth_client/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: auth_client -description: An authentication client implementation -version: 0.1.0+1 -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dependencies: - equatable: ^2.0.5 - rxdart: ^0.28.0 - token_provider: - path: ../token_provider - -dev_dependencies: - mocktail: ^1.0.4 - test: ^1.25.8 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/auth_client/test/src/auth_client_test.dart b/todo/packages/auth_client/test/src/auth_client_test.dart deleted file mode 100644 index a4f4558..0000000 --- a/todo/packages/auth_client/test/src/auth_client_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('AuthClient', () { - late AuthenticationClient repository; - - const validEmail = 'email@example.com'; - const validPassword = 'password'; - - setUp(() { - repository = AuthenticationClient(); - }); - - test('can be instantiated', () { - final repository = AuthenticationClient(); - expect(repository, isNotNull); - expect(repository, isA()); - }); - - test('signs in user correctly', () { - expect( - () async => repository.signInUser( - email: validEmail, - password: validPassword, - ), - returnsNormally, - ); - }); - - test('calls auth status change on AuthenticationClient', () { - final stream = repository.onAuthStatusChanged; - expect(stream, isA>()); - }); - - test('signs out user correctly', () { - expect( - () async => repository.signOut(), - returnsNormally, - ); - }); - - test('fetch user token correctly', () { - expect( - () async => repository.fetchToken(), - returnsNormally, - ); - }); - - test('refreshes user token correctly', () { - expect( - () async => repository.requestTokenRefresh(), - throwsUnimplementedError, - ); - }); - - test('reauthenticates user', () { - expect( - () async => repository.reAuthenticate( - email: validEmail, - password: validPassword, - ), - throwsUnimplementedError, - ); - }); - - test('deletes account', () { - expect( - () async => repository.deleteAccount(), - returnsNormally, - ); - }); - }); -} diff --git a/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart b/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart deleted file mode 100644 index 969fce9..0000000 --- a/todo/packages/auth_client/test/src/exceptions/exceptions_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('SignInFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Invalid arguments'); - final stackTrace = StackTrace.current; - - final signInFailure = SignInFailure(error, stackTrace); - - expect(signInFailure.error, equals(error)); - expect(signInFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('InvalidUserFailure', () { - test('constructs correctly', () { - final error = StateError('User not found'); - final stackTrace = StackTrace.current; - - final invalidUserFailure = InvalidUserFailure(error, stackTrace); - - expect(invalidUserFailure.error, equals(error)); - expect(invalidUserFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('ReAuthenticateFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Could not reauthenticate'); - final stackTrace = StackTrace.current; - - final reauthenticateFailure = ReAuthenticateFailure(error, stackTrace); - - expect(reauthenticateFailure.error, equals(error)); - expect(reauthenticateFailure.stackTrace, equals(stackTrace)); - }); - }); - - group('DeleteAccountFailure', () { - test('constructs correctly', () { - final error = ArgumentError('Invalid arguments'); - final stackTrace = StackTrace.current; - - final deleteAccountFailure = DeleteAccountFailure(error, stackTrace); - - expect(deleteAccountFailure.error, equals(error)); - expect(deleteAccountFailure.stackTrace, equals(stackTrace)); - }); - }); -} diff --git a/todo/packages/auth_client/test/src/models/authentication_event_test.dart b/todo/packages/auth_client/test/src/models/authentication_event_test.dart deleted file mode 100644 index aa8cadf..0000000 --- a/todo/packages/auth_client/test/src/models/authentication_event_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('AuthEventType', () { - test('isSignedIn returns true for signedIn event', () { - final isSignedIn = AuthEventType.signedIn.isSignedIn; - expect(isSignedIn, isTrue); - }); - - test('isSignedOut returns true for signedIn event', () { - final isSignedOut = AuthEventType.signedOut.isSignedOut; - expect(isSignedOut, isTrue); - }); - - test('isSessionExpired returns true for signedIn event', () { - final isSessionExpired = AuthEventType.sessionExpired.isSessionExpired; - expect(isSessionExpired, isTrue); - }); - - test('isUserDeleted returns true for signedIn event', () { - final isUserDeleted = AuthEventType.userDeleted.isUserDeleted; - expect(isUserDeleted, isTrue); - }); - }); - - group('AuthenticationEvent', () { - test('constructor initializes properties', () { - const user = AuthenticationUser(id: '123', email: 'test@example.com'); - const type = AuthEventType.signedIn; - - const event = AuthenticationEvent(type: type, user: user); - - expect(event.user, equals(user)); - expect(event.type, equals(type)); - }); - - test('constructor initializes user as anonymous if null', () { - const type = AuthEventType.signedOut; - - const event = AuthenticationEvent(type: type); - - expect(event.user, equals(AuthenticationUser.anonymous)); - expect(event.type, equals(type)); - }); - - test('props list contains user and type', () { - const user = AuthenticationUser(id: '123', email: 'test@example.com'); - const type = AuthEventType.signedIn; - - const event = AuthenticationEvent(type: type, user: user); - - expect(event.props, containsAll([user, type])); - }); - - test('supports value equality', () { - const eventA = AuthenticationEvent( - user: AuthenticationUser(id: 'A', email: 'A'), - type: AuthEventType.signedIn, - ); - const secondEventA = AuthenticationEvent( - user: AuthenticationUser(id: 'A', email: 'A'), - type: AuthEventType.signedIn, - ); - const eventB = AuthenticationEvent( - user: AuthenticationUser(id: 'B', email: 'B'), - type: AuthEventType.signedIn, - ); - - expect(eventA, equals(secondEventA)); - expect(eventA, isNot(equals(eventB))); - }); - - test('constructor initializes user as anonymous if user is null', () { - const type = AuthEventType.signedOut; - const event = AuthenticationEvent(type: type); - - expect(event.user, equals(AuthenticationUser.anonymous)); - expect(event.type, equals(type)); - }); - }); -} diff --git a/todo/packages/auth_client/test/src/models/authentication_user_test.dart b/todo/packages/auth_client/test/src/models/authentication_user_test.dart deleted file mode 100644 index d6293f1..0000000 --- a/todo/packages/auth_client/test/src/models/authentication_user_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:auth_client/auth_client.dart'; -import 'package:test/test.dart'; - -void main() { - group('AuthenticationUser', () { - test('constructor initializes properties', () { - const id = '123'; - const email = 'test@example.com'; - - const user = AuthenticationUser(id: id, email: email); - - expect(user.id, equals(id)); - expect(user.email, equals(email)); - }); - - test('anonymous user is initialized correctly', () { - const user = AuthenticationUser.anonymous; - - expect(user.id, isEmpty); - expect(user.email, isEmpty); - }); - - test('props list contains id and email', () { - const id = '123'; - const email = 'test@example.com'; - - const user = AuthenticationUser(id: id, email: email); - - expect(user.props, containsAll([id, email])); - }); - - test('supports value equality', () { - const userA = AuthenticationUser(id: 'A', email: 'A'); - const secondUserA = AuthenticationUser(id: 'A', email: 'A'); - const userB = AuthenticationUser(id: 'B', email: 'B'); - - expect(userA, equals(secondUserA)); - expect(userA, isNot(equals(userB))); - }); - - test('isAnonymous returns true for anonymous user', () { - expect(AuthenticationUser.anonymous.isAnonymous, isTrue); - }); - - test( - 'constructor initializes properties with ' - 'empty strings if values are empty', () { - const user = AuthenticationUser.anonymous; - - expect(user.id, isEmpty); - expect(user.email, isEmpty); - }); - }); -} diff --git a/todo/packages/form_inputs/.gitignore b/todo/packages/form_inputs/.gitignore deleted file mode 100644 index d613035..0000000 --- a/todo/packages/form_inputs/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# VSCode related -.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json diff --git a/todo/packages/form_inputs/LICENSE b/todo/packages/form_inputs/LICENSE deleted file mode 100644 index 6f19952..0000000 --- a/todo/packages/form_inputs/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2021 Somnio Software - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/todo/packages/form_inputs/README.md b/todo/packages/form_inputs/README.md deleted file mode 100644 index 1cb3921..0000000 --- a/todo/packages/form_inputs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# form_inputs - -A Dart package which exposes reusable form inputs and validation rules diff --git a/todo/packages/form_inputs/analysis_options.yaml b/todo/packages/form_inputs/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/form_inputs/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/form_inputs/lib/form_inputs.dart b/todo/packages/form_inputs/lib/form_inputs.dart deleted file mode 100644 index c7fe029..0000000 --- a/todo/packages/form_inputs/lib/form_inputs.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'package:formz/formz.dart'; - -export 'src/address_label.dart'; -export 'src/email.dart'; -export 'src/form_status_enum.dart'; -export 'src/name.dart'; -export 'src/otp_code.dart'; -export 'src/password.dart'; -export 'src/phone_number.dart'; -export 'src/username.dart'; -export 'src/zipcode.dart'; diff --git a/todo/packages/form_inputs/lib/src/address_label.dart b/todo/packages/form_inputs/lib/src/address_label.dart deleted file mode 100644 index 16adfba..0000000 --- a/todo/packages/form_inputs/lib/src/address_label.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; - -/// Address Label Form Input Validation Error -enum AddressLabelValidationError { - /// Address Label is invalid (generic validation error) - invalid, - - /// Address Label is empty - empty, -} - -/// {@template address_label} -/// Reusable address label form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class AddressLabel extends FormzInput { - /// {@macro address_label} - /// Call super.pure to represent an unmodified form input. - const AddressLabel.pure() : super.pure(''); - - /// {@macro address_label} - /// Call super.dirty to represent a modified form input. - const AddressLabel.dirty([super.value = '']) : super.dirty(); - - /// Override validator to handle validating a given input value. - @override - AddressLabelValidationError? validator(String? value) { - if (value == null || value.trim().isEmpty) { - return AddressLabelValidationError.empty; - } - if (value.trim().isNotEmpty) return null; - return AddressLabelValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/lib/src/email.dart b/todo/packages/form_inputs/lib/src/email.dart deleted file mode 100644 index 6e472bc..0000000 --- a/todo/packages/form_inputs/lib/src/email.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:formz/formz.dart'; - -/// Email Form Input Validation Error -enum EmailValidationError { - /// Email is invalid (generic validation error) - invalid, - - /// Email is empty - empty, -} - -/// {@template email} -/// Reusable email form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class Email extends FormzInput - with EquatableMixin { - /// {@macro email} - /// Call super.pure to represent an unmodified form input. - const Email.pure() : super.pure(''); - - /// {@macro email} - /// Call super.dirty to represent a modified form input. - const Email.dirty([super.value = '']) : super.dirty(); - - static final RegExp _emailRegExp = RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$', - ); - - /// Override validator to handle validating a given input value. - @override - EmailValidationError? validator(String value) { - if (value.isEmpty) { - return EmailValidationError.empty; - } - if (!_emailRegExp.hasMatch(value)) { - return EmailValidationError.invalid; - } - return null; - } - - @override - List get props => [value]; -} diff --git a/todo/packages/form_inputs/lib/src/form_status_enum.dart b/todo/packages/form_inputs/lib/src/form_status_enum.dart deleted file mode 100644 index 4e69470..0000000 --- a/todo/packages/form_inputs/lib/src/form_status_enum.dart +++ /dev/null @@ -1,20 +0,0 @@ -/// Enum to manage the state of a form -enum FormStatus { - /// Used when all the inputs are pure - pure, - - /// Used when all the inputs are valid - valid, - - /// Used when one or more inputs are invalid - invalid, - - /// Used when one or more inputs are dirty - submissionInProgress, - - /// Used when form is waiting for an external process - submissionLoading, - - /// Used when form is valid and all external process finished - submissionLoaded, -} diff --git a/todo/packages/form_inputs/lib/src/name.dart b/todo/packages/form_inputs/lib/src/name.dart deleted file mode 100644 index 65d42cf..0000000 --- a/todo/packages/form_inputs/lib/src/name.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:formz/formz.dart'; - -/// Name Form Input Validation Error -enum NameValidationError { - /// Name is invalid (generic validation error) - invalid, - - /// Name is empty - empty, -} - -/// {@template name} -/// Reusable name form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class Name extends FormzInput { - /// {@macro name} - /// Call super.pure to represent an unmodified form input. - const Name.pure() : super.pure(''); - - /// {@macro name} - /// Call super.dirty to represent a modified form input. - const Name.dirty([super.value = '']) : super.dirty(); - - static final _nameRegExp = RegExp( - r"^[\p{L} ,.'-]*$", - caseSensitive: false, - unicode: true, - dotAll: true, - ); - - /// Override validator to handle validating a given input value. - @override - NameValidationError? validator(String? value) { - if (value == null || value.isEmpty) return NameValidationError.empty; - - return _nameRegExp.hasMatch(value) ? null : NameValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/lib/src/otp_code.dart b/todo/packages/form_inputs/lib/src/otp_code.dart deleted file mode 100644 index 21dc529..0000000 --- a/todo/packages/form_inputs/lib/src/otp_code.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; - -/// OTP Form Input Validation Error -enum OtpValidationError { - /// OTP is invalid (generic validation error) - invalid, - - /// OTP is empty - empty, -} - -/// {@template otp} -/// Reusable OTP form input that is valid only for 6 digits. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class Otp extends FormzInput { - /// {@macro otp} - /// Call super.pure to represent an unmodified form input. - const Otp.pure() : super.pure(''); - - /// {@macro otp} - /// Call super.dirty to represent a modified form input. - const Otp.dirty([super.value = '']) : super.dirty(); - - /// Override validator to handle validating a given input value. - @override - OtpValidationError? validator(String? value) { - if (value == null) return OtpValidationError.empty; - return value.length >= 6 ? null : OtpValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/lib/src/password.dart b/todo/packages/form_inputs/lib/src/password.dart deleted file mode 100644 index 228918a..0000000 --- a/todo/packages/form_inputs/lib/src/password.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:formz/formz.dart'; - -/// Password Form Input Validation Error -enum PasswordValidationError { - /// Password is empty - empty, - - /// Password is shorter than 8 characters - notLongEnough, - - /// Password does not contain a digit - noDigit, - - /// Password does not contain an upper case - noUpperCase, - - /// Password does not contain a lower case - noLowerCase, - - /// Password does not contain a special character - noSpecialCharacter, -} - -/// {@template password} -/// Reusable password form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class Password extends FormzInput { - /// {@macro password} - /// Call super.pure to represent an unmodified form input. - const Password.pure() : super.pure(''); - - /// {@macro password} - /// Call super.dirty to represent a modified form input. - const Password.dirty([super.value = '']) : super.dirty(); - - static final RegExp _upperCaseRegex = RegExp('[A-Z]'); - - static final RegExp _lowerCaseRegex = RegExp('[a-z]'); - - static final RegExp _specialCharactersRegex = - RegExp(r'[!@#$%^&*(),.?":{}|<>]'); - - static final RegExp _digitsRegex = RegExp('[0-9]'); - - /// Override validator to handle validating a given input value. - @override - PasswordValidationError? validator(String value) { - if (value.isEmpty) { - return PasswordValidationError.empty; - } - if (value.length < 8) { - return PasswordValidationError.notLongEnough; - } - if (!value.contains(_digitsRegex)) { - return PasswordValidationError.noDigit; - } - if (!value.contains(_upperCaseRegex)) { - return PasswordValidationError.noUpperCase; - } - if (!value.contains(_lowerCaseRegex)) { - return PasswordValidationError.noLowerCase; - } - if (!value.contains(_specialCharactersRegex)) { - return PasswordValidationError.noSpecialCharacter; - } - return null; - } -} diff --git a/todo/packages/form_inputs/lib/src/phone_number.dart b/todo/packages/form_inputs/lib/src/phone_number.dart deleted file mode 100644 index 073aaec..0000000 --- a/todo/packages/form_inputs/lib/src/phone_number.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; - -/// PhoneNumber Form Input Validation Error -enum PhoneNumberValidationError { - /// PhoneNumber is invalid (it can only have digits and + sign) - invalid, - - /// Phone number is too short - tooShort, - - /// Phone number is too long - tooLong, - - /// PhoneNumber is empty - empty, -} - -/// {@template phone_number} -/// Reusable PhoneNumber form input that is valid only for 10 digits. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class PhoneNumber extends FormzInput { - /// {@macro phone_number} - /// Call super.pure to represent an unmodified form input. - const PhoneNumber.pure() : super.pure(''); - - /// {@macro phone_number} - /// Call super.dirty to represent a modified form input. - const PhoneNumber.dirty([super.value = '']) : super.dirty(); - - static final _phoneRegex = RegExp(r'^[0-9]{10}$'); - - @override - PhoneNumberValidationError? validator(String value) { - if (value.isEmpty) return PhoneNumberValidationError.empty; - return _phoneRegex.hasMatch(value) - ? null - : PhoneNumberValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/lib/src/src.dart b/todo/packages/form_inputs/lib/src/src.dart deleted file mode 100644 index acda98a..0000000 --- a/todo/packages/form_inputs/lib/src/src.dart +++ /dev/null @@ -1,8 +0,0 @@ -export 'address_label.dart'; -export 'email.dart'; -export 'form_status_enum.dart'; -export 'name.dart'; -export 'otp_code.dart'; -export 'password.dart'; -export 'phone_number.dart'; -export 'zipcode.dart'; diff --git a/todo/packages/form_inputs/lib/src/username.dart b/todo/packages/form_inputs/lib/src/username.dart deleted file mode 100644 index 619cf23..0000000 --- a/todo/packages/form_inputs/lib/src/username.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:formz/formz.dart'; - -/// Username Form Input Validation Error -enum UsernameValidationError { - /// Username is invalid (generic validation error) - invalid, - - /// Username is empty - empty, -} - -/// {@template username} -/// Reusable username form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class Username extends FormzInput { - /// {@macro Username} - /// Call super.pure to represent an unmodified form input. - const Username.pure() : super.pure(''); - - /// {@macro Username} - /// Call super.dirty to represent a modified form input. - const Username.dirty([super.value = '']) : super.dirty(); - - static final _usernameRegExp = RegExp( - r'^^[a-zA-Z0-9_-]+$', - caseSensitive: false, - unicode: true, - dotAll: true, - ); - - /// Override validator to handle validating a given input value. - @override - UsernameValidationError? validator(String? value) { - if (value == null || value.isEmpty) return UsernameValidationError.empty; - - return _usernameRegExp.hasMatch(value) - ? null - : UsernameValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/lib/src/zipcode.dart b/todo/packages/form_inputs/lib/src/zipcode.dart deleted file mode 100644 index dec3fbb..0000000 --- a/todo/packages/form_inputs/lib/src/zipcode.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:formz/formz.dart'; - -/// Zip Code Form Input validation error -enum ZipCodeValidationError { - /// Zip Code is invalid (generic validation error) - invalid, - - ///Zip code is empty - empty, -} - -/// {@template zip_code} -/// Reusable zip code form input. -/// Extends FormzInput and provides the input type and error type. -/// {@endtemplate} -class ZipCode extends FormzInput { - /// {@macro zip_code} - /// Call super.pure to represent an unmodified form input. - const ZipCode.pure({bool required = true}) - : _required = required, - super.pure(''); - - /// {@macro zip_code} - /// Call super.dirty to represent a modified form input. - const ZipCode.dirty({String value = '', bool required = true}) - : _required = required, - super.dirty(value); - - final bool _required; - - /// Matches 5-digit only codes (e.g. 12345) - static final RegExp _zipCodeRegExp = RegExp(r'(^\d{5}(-\d{4})?$)'); - - /// Override validator to handle validating a given input value. - @override - ZipCodeValidationError? validator(String value) { - if (!_required && value.isEmpty) { - return null; - } - if (_required && value.isEmpty) { - return ZipCodeValidationError.empty; - } - - return _zipCodeRegExp.hasMatch(value) - ? null - : ZipCodeValidationError.invalid; - } -} diff --git a/todo/packages/form_inputs/pubspec.yaml b/todo/packages/form_inputs/pubspec.yaml deleted file mode 100644 index 859f24e..0000000 --- a/todo/packages/form_inputs/pubspec.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: form_inputs -description: A Dart package which exposes reusable form inputs and validation rules -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dependencies: - equatable: ^2.0.5 - formz: ^0.7.0 - -dev_dependencies: - test: ^1.25.8 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/form_inputs/test/address_label_test.dart b/todo/packages/form_inputs/test/address_label_test.dart deleted file mode 100644 index be25e04..0000000 --- a/todo/packages/form_inputs/test/address_label_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const addressString = '1142 Main Street, Seattle, WA 98101'; - group('Address Label', () { - group('constructors', () { - test('pure creates correct instance', () { - const address = AddressLabel.pure(); - expect(address.value, ''); - expect(address.isPure, true); - }); - - test('dirty creates correct instance', () { - const address = AddressLabel.dirty(addressString); - expect(address.value, addressString); - expect(address.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when address is empty', () { - expect( - const AddressLabel.dirty().error, - AddressLabelValidationError.empty, - ); - }); - - test('returns invalid error when address is malformed', () { - expect( - const AddressLabel.dirty('test').error, - isNull, - ); - }); - - test('is valid when address is valid', () { - expect( - const AddressLabel.dirty(addressString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/email_test.dart b/todo/packages/form_inputs/test/email_test.dart deleted file mode 100644 index 3442198..0000000 --- a/todo/packages/form_inputs/test/email_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const emailString = 'test@gmail.com'; - group('Email', () { - group('constructors', () { - test('pure creates correct instance', () { - final email = Email.pure(); - expect(email.value, ''); - expect(email.isPure, true); - }); - - test('dirty creates correct instance', () { - final email = Email.dirty(emailString); - expect(email.value, emailString); - expect(email.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when email is empty', () { - expect( - Email.dirty().error, - EmailValidationError.empty, - ); - }); - - test('returns invalid error when email is malformed', () { - expect( - Email.dirty('test').error, - EmailValidationError.invalid, - ); - }); - - test('is valid when email is valid', () { - expect( - Email.dirty(emailString).error, - isNull, - ); - }); - - test('equality should work correctly', () { - const email1 = Email.dirty('test1@email.com'); - const email2 = Email.dirty('test1@email.com'); - const email3 = Email.dirty('test2@email.com'); - - expect( - email1, - equals(email2), - ); - expect( - email1 == email3, - isFalse, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/name_test.dart b/todo/packages/form_inputs/test/name_test.dart deleted file mode 100644 index 77420f5..0000000 --- a/todo/packages/form_inputs/test/name_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const nameString = 'test'; - group('Name', () { - group('constructors', () { - test('pure creates correct instance', () { - const name = Name.pure(); - expect(name.value, ''); - expect(name.isPure, true); - }); - - test('dirty creates correct instance', () { - const name = Name.dirty(nameString); - expect(name.value, nameString); - expect(name.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when name is empty', () { - expect( - const Name.dirty().error, - NameValidationError.empty, - ); - }); - - test('returns invalid error when name is malformed', () { - expect( - const Name.dirty('test').error, - isNull, - ); - }); - - test('is valid when name is valid', () { - expect( - const Name.dirty(nameString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/otp_code_test.dart b/todo/packages/form_inputs/test/otp_code_test.dart deleted file mode 100644 index 9acebae..0000000 --- a/todo/packages/form_inputs/test/otp_code_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const otpString = '123456'; - group('Otp', () { - group('constructors', () { - test('pure creates correct instance', () { - const otp = Otp.pure(); - expect(otp.value, ''); - expect(otp.isPure, true); - }); - - test('dirty creates correct instance', () { - const otp = Otp.dirty(otpString); - expect(otp.value, otpString); - expect(otp.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when otp is empty', () { - expect( - const Otp.dirty().error, - OtpValidationError.invalid, - ); - }); - - test('returns invalid error when otp is malformed', () { - expect( - const Otp.dirty('test').error, - OtpValidationError.invalid, - ); - }); - - test('is valid when otp is valid', () { - expect( - const Otp.dirty(otpString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/password_test.dart b/todo/packages/form_inputs/test/password_test.dart deleted file mode 100644 index 46f1fc9..0000000 --- a/todo/packages/form_inputs/test/password_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const passwordString = '12345678Aa!'; - group('Password', () { - group('constructors', () { - test('pure creates correct instance', () { - final email = Password.pure(); - expect(email.value, ''); - expect(email.isPure, true); - }); - - test('dirty creates correct instance', () { - final email = Password.dirty(passwordString); - expect(email.value, passwordString); - expect(email.isPure, false); - }); - }); - - group('validator', () { - test('returns invalid error when password is empty', () { - expect( - Password.dirty().error, - PasswordValidationError.empty, - ); - }); - - test('returns invalid error when password is less than 6 characters', () { - expect( - Password.dirty('test').error, - PasswordValidationError.notLongEnough, - ); - }); - - test('is valid when password is valid', () { - expect( - Password.dirty(passwordString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/phone_number_test.dart b/todo/packages/form_inputs/test/phone_number_test.dart deleted file mode 100644 index 087254c..0000000 --- a/todo/packages/form_inputs/test/phone_number_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const phoneString = '+16505551234'; - group('Phone Number', () { - group('constructors', () { - test('pure creates correct instance', () { - const phone = PhoneNumber.pure(); - expect(phone.value, ''); - expect(phone.isPure, true); - }); - - test('dirty creates correct instance', () { - const phone = PhoneNumber.dirty(phoneString); - expect(phone.value, phoneString); - expect(phone.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when phone is empty', () { - expect( - const PhoneNumber.dirty().error, - PhoneNumberValidationError.empty, - ); - }); - - test('returns invalid error when phone is malformed', () { - expect( - const PhoneNumber.dirty('test').error, - PhoneNumberValidationError.invalid, - ); - }); - - test('is valid when phone is valid', () { - expect( - const PhoneNumber.dirty(phoneString).error, - PhoneNumberValidationError.invalid, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/username_test.dart b/todo/packages/form_inputs/test/username_test.dart deleted file mode 100644 index ea13414..0000000 --- a/todo/packages/form_inputs/test/username_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const userNameString = 'username'; - group('Username', () { - group('constructors', () { - test('pure creates correct instance', () { - const userName = Username.pure(); - expect(userName.value, ''); - expect(userName.isPure, true); - }); - - test('dirty creates correct instance', () { - const userName = Username.dirty(userNameString); - expect(userName.value, userNameString); - expect(userName.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when userName is empty', () { - expect( - const Username.dirty().error, - UsernameValidationError.empty, - ); - }); - - test('returns invalid error when userName is malformed', () { - expect( - const Username.dirty('test').error, - isNull, - ); - }); - - test('is valid when userName is valid', () { - expect( - const Username.dirty(userNameString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/form_inputs/test/zipcode_test.dart b/todo/packages/form_inputs/test/zipcode_test.dart deleted file mode 100644 index 057712f..0000000 --- a/todo/packages/form_inputs/test/zipcode_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:form_inputs/form_inputs.dart'; -import 'package:test/test.dart'; - -void main() { - const zipString = '12345'; - - group('Zip Code', () { - group('constructors', () { - test('pure creates correct instance', () { - const zipcode = ZipCode.pure(); - expect(zipcode.value, ''); - expect(zipcode.isPure, true); - }); - - test('dirty creates correct instance', () { - const zipcode = ZipCode.dirty(value: zipString); - expect(zipcode.value, zipString); - expect(zipcode.isPure, false); - }); - }); - - group('validator', () { - test('returns empty error when zipcode is empty', () { - expect( - const ZipCode.dirty().error, - ZipCodeValidationError.empty, - ); - }); - - test('returns invalid error when zipcode is malformed', () { - expect( - const ZipCode.dirty(value: 'test').error, - ZipCodeValidationError.invalid, - ); - }); - - test('is valid when zipcode is valid', () { - expect( - const ZipCode.dirty(value: zipString).error, - isNull, - ); - }); - }); - }); -} diff --git a/todo/packages/todo_ui/.gitignore b/todo/packages/todo_ui/.gitignore deleted file mode 100644 index dfb7f59..0000000 --- a/todo/packages/todo_ui/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/pubspec.lock -**/doc/api/ -.dart_tool/ -.packages -build/ \ No newline at end of file diff --git a/todo/packages/todo_ui/README.md b/todo/packages/todo_ui/README.md deleted file mode 100644 index a35c701..0000000 --- a/todo/packages/todo_ui/README.md +++ /dev/null @@ -1,121 +0,0 @@ -## Todo UI package - -Todo UI package containing a general design system. - -#### Output 📦 - -``` -├── todo_ui -│ ├── assets -│ │ ├── fonts -│ │ ├── icons -│ │ ├── images -│ ├── gallery -│ ├── lib -│ │ └── src -│ │ ├── colors -│ │ ├── icons -│ │ │ ├── todo_icons_dark.dart -│ │ │ ├── todo_icons_light.dart -│ │ │ └── todo_icons.dart -│ │ ├── spacing -│ │ ├── theme -│ │ ├── typography -│ │ └── widgets -│ │ ├── todo_app_bar.dart -│ │ ├── todo_outlined_button.dart -│ │ └── todo_text_field.dart -│ ├── todo_ui.dart -│ ├── .gitignore -│ ├── analysis_options.yaml -│ └── pubspec.yaml -└── ... -``` - -#### Colors - -The colors and their names come from Material 3 flutter. They should be modified to meet the project colors requirements. - -#### Spacing - -The spacings and their names are general examples, the user can add other necessary spacing. - -#### Theme - -A theme is a collection of attributes that's applied to an entire project not just an individual view. When you apply the theme, every view in the project applies each of the theme's attributes that it supports. The theme here may be dark (black background) or light (white background). - -#### Typography - -In this project, the only fonts assets being used in this case are `NotoSansDisplay-Regular` and `NotoSansDisplay-Semibold`. - -#### Widgets - -In the output of the brick, there are a textfield, an outline button and an appbar. All of which extend an example. The developer can delete these and add specific project widgets extending the widgets that flutter already provides or that they reflect the identity of the project. - -#### Adding new assets - -Everytime the user adds a new asset (such as icons, images, fonts, gifs) he must: - -```dart - cd todo_ui - flutter pub get - flutter pub run build_runner build -``` - -The `assets.gen.dart`, `fonts.gen.dart` will update. - -#### Assets usage Example - -The correct way to use the assets, such as icons, gifs, pictures after running `flutter pub run build_runner build` inside the package directory is: - -1) Inside `todo_icons.dart`: - - -```dart - SvgPicture backIcon({double? size, Color? color}) { - return Assets.icons.backIcon.svg( - color: color, - height: size ?? 14, - width: size ?? 14, - ); - } -``` -2) In `todo_icons_light.dart` add the icon with its color respectively of the theme. - - -```dart -@override - SvgPicture backIcon({double? size, Color? color}) { - return super.backIcon( - color: color ?? ProjectNameColors.black, - size: size ?? 14, - ); - } -``` - -3) In `todo_icons_dark.dart` add the icon with its color respectively of the theme. - - -```dart -@override - SvgPicture backIcon({double? size, Color? color}) { - return super.backIcon( - color: color ?? AppColors.white, - size: size ?? 14, - ); - } -``` - -4) Set the size and color of the icon when using it in the project UI: - -```dart -Theme.of(context).icons.backIcon( - size: 30, - ), -``` - -#### Gallery - -The gallery shows different visual examples of some items from the ui package as well as some to use the project theme properly. - -The theme can be changed in the gallery by clicking on the icon of the moon/sun at the top. The idea is that the developer can see the difference in colors, buttons and other widgets according to the theme chosen between dark and light. \ No newline at end of file diff --git a/todo/packages/todo_ui/analysis_options.yaml b/todo/packages/todo_ui/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/todo_ui/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf b/todo/packages/todo_ui/assets/fonts/NotoSansDisplay-Regular.ttf deleted file mode 100644 index d7201de3963088ee609214f688ca5a126b0e3ea7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113460 zcmbq+2Ylm4mA~dE+j5m9%j&&LmMmG_wk*lL_bpzp%l4K{ve{(QNFf!{XrYCWP71wq z^d8^{B>@6QfFlVI?no|22$1YS{PX|&<|oA_DaAw;`jfOv56}WAK0_scK2`pAY(g*(Uv}phWaT(liOW>xPXV7D*T`g= z*>jKW+jHOnRZ_+#Z<5JszINW8%Py#F78>yRF#0>r-*eu9)D7=BBxC0#@%LLUICjyA zD|cRWjf`FL4VkP(EfZwGi~UM?3^*xdvV_{H_Bz$h9qc;s zvM=*~jWSurElERIR?hX>Y+jGU0iwuq>|fbX z=?NOSTkgc`eVVj})oL6bY?c;m+_^Dv@#Hl~C}Pwh|xN zw=LJa#W&!)-rMiFZ@Hd*_P)NK;9uW;y-(s_?|t`Sj2)}r zWq%Z2m3d@g87Pv8Fn`L%^r?tEl}u++2`xS-(5STu7@0!r&g5Ati3YinX`AuK|4!|( zI}Wr(2g2IDTe{*~+-Cc*FTW+UXQ8Vr71x9VU9o6Wizmj^joVZP_L$PylJSggt=uuk zw2h+;8u|A%p+bCcuJ+hht3ocL(@@9k6)jGa*H|l)RmxgdUlIN&JSnS}8Dth&i!6;< zE)qEcMACX!m9|-zh^JFYpO?9rCT;^>n1a*(`h~&a{?XRFqi@*laJcc0y;ZzzjGgF+ zbUO#6Ke!wYm&fH2p1k;+b1qFE7(D0V{LJ+9?99ycOmZcSiTSV8zMF;`AOmDpkh>(#XNBg75Y5OB$P~{cl(uc2_v*b1fZ=^f}DhQM$QL-t{4Ebjn7YX ze%fHK-Jy(nZn=0$7t8ecdUq$?-Qi}he%a@U8V5Q$w_4xli@TlaXju69%t&E}=S%jP zX!rTSw{F$C4a(LXg~Vv5*^tb6MrXxqw4tOY)7xSdgs|U}^SW{YPejIu2W9W$JSZa$ zB<_lwX5ukb7U8GlCaXKk9#HhlDgEmOnW&ik<(c>{1UFo$I&tx_D!kk2HCUu*SjT?7zT0DjCEB zYgP&+UP*wND?#~0Tv~K)my)snGN!h7jqhs<@0}Un7ZdL=cg12|X6Yw8ICs;o_>PAz zI`QD#+=D0fUf0uo{ocLT_Vip!7|-Cx3?A7alR4dLnpq`lWd2No&1_==Q<|EFB3~G0 zf3*hO8kJs^&%TGg)ruc4U;pSbg(nd!_$^QoOJEqhz%CqrAh3f=9a-tKl!XUq^X z6ee4b92VSdgW;Nr+8smrJ!!RWR%aaRZOxd*uoRLVjpnAVW>@omng=_t6x6?Oy>HRa?`ltO}4rh1TfsqF=;ukUUD(s)k znR05`)vQUpgMCN*i|~f&{*^aiElE88BzCo^EVoV}1#L4l`6RxJ!ZOd~Br2sP5M@e1 zLn0IMh_fQA&n_h09qwR%AhWM;vA}H zKx*J4X(2vHA!rKaa!9?IOgdFIu_t^sn~&5IL1u+a{Gw2T#HFGXmYzvMi-ZrmhPUsiJe~p1|EpWPJm9ki3eI)%ByW)?0K=DeMP*F?LP5%{DsL+ z--Bl+Wlym`u>Sx*K-bcMQ!4RFvQLWB?33)ip`q^$4Q+�kBGKWrXb#?+38{Z{7Ww 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�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 - - diff --git a/todo/packages/todo_ui/assets/icons/email_outline.svg b/todo/packages/todo_ui/assets/icons/email_outline.svg deleted file mode 100644 index cb905d5..0000000 --- a/todo/packages/todo_ui/assets/icons/email_outline.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/todo/packages/todo_ui/assets/images/icon.png b/todo/packages/todo_ui/assets/images/icon.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/todo/packages/todo_ui/gallery/.gitignore b/todo/packages/todo_ui/gallery/.gitignore deleted file mode 100644 index 9d532b1..0000000 --- a/todo/packages/todo_ui/gallery/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json diff --git a/todo/packages/todo_ui/gallery/.metadata b/todo/packages/todo_ui/gallery/.metadata deleted file mode 100644 index 182ccca..0000000 --- a/todo/packages/todo_ui/gallery/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 78910062997c3a836feee883712c241a5fd22983 - channel: stable - -project_type: app diff --git a/todo/packages/todo_ui/gallery/README.md b/todo/packages/todo_ui/gallery/README.md deleted file mode 100644 index 84fc389..0000000 --- a/todo/packages/todo_ui/gallery/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## Gallery - -Gallery project to showcase todo_ui - -To look through the galleryApp its only necessary to open `main.dart` and then run. - -The developer can look through the gallery with darkTheme or lightTheme by clicking on the icon of the moon/sun at the top. - - diff --git a/todo/packages/todo_ui/gallery/analysis_options.yaml b/todo/packages/todo_ui/gallery/analysis_options.yaml deleted file mode 100644 index 2773f5a..0000000 --- a/todo/packages/todo_ui/gallery/analysis_options.yaml +++ /dev/null @@ -1,5 +0,0 @@ -include: package:very_good_analysis/analysis_options.5.1.0.yaml - -linter: - rules: - public_member_api_docs: false diff --git a/todo/packages/todo_ui/gallery/android/.gitignore b/todo/packages/todo_ui/gallery/android/.gitignore deleted file mode 100644 index 6f56801..0000000 --- a/todo/packages/todo_ui/gallery/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/todo/packages/todo_ui/gallery/android/app/build.gradle b/todo/packages/todo_ui/gallery/android/app/build.gradle deleted file mode 100644 index 18939ff..0000000 --- a/todo/packages/todo_ui/gallery/android/app/build.gradle +++ /dev/null @@ -1,68 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion flutter.compileSdkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.todo_ui.gallery" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 3fbe6ee..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 8fb017c..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt b/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt deleted file mode 100644 index ec3a38f..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/kotlin/com/ui/gallery/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.todo.ui.gallery - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/todo/packages/todo_ui/gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 3db14bb..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml b/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml deleted file mode 100644 index d460d1e..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml b/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 3fbe6ee..0000000 --- a/todo/packages/todo_ui/gallery/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/todo/packages/todo_ui/gallery/android/build.gradle b/todo/packages/todo_ui/gallery/android/build.gradle deleted file mode 100644 index 4256f91..0000000 --- a/todo/packages/todo_ui/gallery/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/todo/packages/todo_ui/gallery/android/gradle.properties b/todo/packages/todo_ui/gallery/android/gradle.properties deleted file mode 100644 index 94adc3a..0000000 --- a/todo/packages/todo_ui/gallery/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties b/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index bc6a58a..0000000 --- a/todo/packages/todo_ui/gallery/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/todo/packages/todo_ui/gallery/android/settings.gradle b/todo/packages/todo_ui/gallery/android/settings.gradle deleted file mode 100644 index 44e62bc..0000000 --- a/todo/packages/todo_ui/gallery/android/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/todo/packages/todo_ui/gallery/ios/.gitignore b/todo/packages/todo_ui/gallery/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/todo/packages/todo_ui/gallery/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist b/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 8d4492f..0000000 --- a/todo/packages/todo_ui/gallery/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 9.0 - - diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig b/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/todo/packages/todo_ui/gallery/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig b/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/todo/packages/todo_ui/gallery/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/todo/packages/todo_ui/gallery/ios/Podfile b/todo/packages/todo_ui/gallery/ios/Podfile deleted file mode 100644 index 1e8c3c9..0000000 --- a/todo/packages/todo_ui/gallery/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 08086b8..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,481 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1300; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.todo_ui.gallery; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index c87d15a..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a1..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift b/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0bca859a3f474b03065bef75ba58a9e4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7edb86cdfe0d15b4b0d98334a86163658..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1716 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 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5cee1c98386d13b17e89f719e83555b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609df07bf62e5100a53a01510388bd2b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609df07bf62e5100a53a01510388bd2b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a5a98e212cca15ea7bf2ab5de5108680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3831 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 diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39da7941ef3f6dcb7f06a192d8dcb308d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard b/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c2851..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Info.plist b/todo/packages/todo_ui/gallery/ios/Runner/Info.plist deleted file mode 100644 index 0a43b23..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Gallery - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - gallery - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UIViewControllerBasedStatusBarAppearance - - CADisableMinimumFrameDurationOnPhone - - - diff --git a/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h b/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/todo/packages/todo_ui/gallery/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/todo/packages/todo_ui/gallery/lib/colors/colors.dart b/todo/packages/todo_ui/gallery/lib/colors/colors.dart deleted file mode 100644 index 0b9c7d0..0000000 --- a/todo/packages/todo_ui/gallery/lib/colors/colors.dart +++ /dev/null @@ -1 +0,0 @@ -export 'colors_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart b/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart deleted file mode 100644 index a1c900e..0000000 --- a/todo/packages/todo_ui/gallery/lib/colors/colors_page.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class ColorsPage extends StatelessWidget { - const ColorsPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const ColorsPage()); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final colorItems = [ - _ColorTextContainer( - color: theme.colorScheme.primary, - name: 'Primary ', - ), - _ColorTextContainer( - color: theme.colorScheme.onPrimary, - name: 'On primary ', - ), - _ColorTextContainer( - color: theme.colorScheme.primaryContainer, - name: 'primary Container', - ), - _ColorTextContainer( - color: theme.colorScheme.onPrimaryContainer, - name: 'on primary container', - ), - _ColorTextContainer( - color: theme.colorScheme.secondary, - name: 'Secondary ', - ), - _ColorTextContainer( - color: theme.colorScheme.onSecondary, - name: 'On Secondary ', - ), - _ColorTextContainer( - color: theme.colorScheme.secondaryContainer, - name: 'Secondary container', - ), - _ColorTextContainer( - color: theme.colorScheme.onSecondaryContainer, - name: 'On secondary container', - ), - _ColorTextContainer( - color: theme.colorScheme.tertiary, - name: 'Tertiary ', - ), - _ColorTextContainer( - color: theme.colorScheme.onTertiary, - name: 'On tertiary ', - ), - _ColorTextContainer( - color: theme.colorScheme.tertiaryContainer, - name: 'Tertiary container ', - ), - _ColorTextContainer( - color: theme.colorScheme.onTertiaryContainer, - name: 'On tertiary container ', - ), - _ColorTextContainer( - color: theme.colorScheme.error, - name: 'Error ', - ), - _ColorTextContainer( - color: theme.colorScheme.onError, - name: 'On error ', - ), - _ColorTextContainer( - color: theme.colorScheme.errorContainer, - name: 'Error container', - ), - _ColorTextContainer( - color: theme.colorScheme.onErrorContainer, - name: 'on error container', - ), - _ColorTextContainer( - color: theme.colorScheme.surface, - name: 'Background', - textColor: theme.colorScheme.onSurface, - ), - _ColorTextContainer( - color: theme.colorScheme.onSurface, - name: 'On background', - textColor: theme.colorScheme.surface, - ), - _ColorTextContainer( - color: theme.colorScheme.surface, - name: 'Surface', - textColor: theme.colorScheme.onSurface, - ), - _ColorTextContainer( - color: theme.colorScheme.onSurface, - name: 'On surface', - textColor: theme.colorScheme.surface, - ), - _ColorTextContainer( - color: theme.colorScheme.surfaceContainerHighest, - name: 'Sureface Variant', - textColor: UIColors.black, - ), - _ColorTextContainer( - color: theme.colorScheme.onSurfaceVariant, - name: 'On Sureface Variant', - ), - _ColorTextContainer( - color: theme.colorScheme.inversePrimary, - name: 'Inverse primary', - ), - ]; - - return Scaffold( - appBar: AppBar( - title: Text( - 'ColorTheme based on Material Flutter'.toUpperCase(), - ), - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: colorItems.map((colorContainer) { - return colorContainer; - }).toList(), - ), - ), - ); - } -} - -class _ColorTextContainer extends StatelessWidget { - const _ColorTextContainer({ - required this.color, - required this.name, - this.textColor, - }); - - final Color color; - final Color? textColor; - final String name; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Container( - padding: const EdgeInsets.all( - 10, - ), - margin: const EdgeInsets.symmetric( - horizontal: UISpacing.lg * 2, - vertical: UISpacing.xs, - ), - width: UISpacing.spaceUnit*100, - height: UISpacing.xxlg, - decoration: BoxDecoration( - color: color, - border: Border.all( - color: theme.colorScheme.onSurface, - ), // Set border width - borderRadius: const BorderRadius.all( - Radius.circular(UISpacing.xxs * 5), - ), - ), - child: Center( - child: Text( - name.toUpperCase(), - maxLines: 2, - style: theme.textTheme.labelLarge?.copyWith( - fontSize: 10, - fontWeight: FontWeight.w600, - color: textColor ?? UIColors.black, - ), - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/main.dart b/todo/packages/todo_ui/gallery/lib/main.dart deleted file mode 100644 index 60e8ec4..0000000 --- a/todo/packages/todo_ui/gallery/lib/main.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gallery/colors/colors.dart'; -import 'package:gallery/spacing/spacing.dart'; -import 'package:gallery/typography/typography.dart'; -import 'package:gallery/widgets/widgets.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() => runApp(const MyApp()); - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - bool _isDark = false; - - void _toggleTheme() { - setState(() { - _isDark = !_isDark; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Example Gallery', - theme: _isDark ? UITheme().darkTheme : UITheme().lightTheme, - home: RootPage(toggleTheme: _toggleTheme), - ); - } -} - -class RootPage extends StatelessWidget { - const RootPage({ - required this.toggleTheme, - super.key, - }); - - final VoidCallback toggleTheme; - - @override - Widget build(BuildContext context) { - final pages = [ - _ListItem( - icon: const Icon(Icons.color_lens), - title: const Text('Colors'), - subtitle: const Text('Some predefined colors'), - onTap: () => Navigator.of(context).push(ColorsPage.route()), - ), - _ListItem( - icon: const Icon(Icons.text_format), - title: const Text('Typography'), - subtitle: const Text('Some predefined text styles'), - onTap: () => Navigator.of(context).push(TypographyPage.route()), - ), - _ListItem( - icon: const Icon(Icons.border_vertical), - title: const Text('Spacing'), - subtitle: const Text('All of the predefined spacings'), - onTap: () => Navigator.of(context).push(SpacingPage.route()), - ), - _ListItem( - icon: const Icon(Icons.widgets), - title: const Text('Widgets'), - subtitle: const Text('Some predefined custom widgets'), - onTap: () => Navigator.of(context).push(WidgetsPage.route()), - ), - ]; - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - title: Text( - 'Example Theme Gallery'.toUpperCase(), - ), - titleTextStyle: theme.appBarTheme.titleTextStyle, - leading: IconButton( - onPressed: toggleTheme, - icon: Icon( - theme.brightness == Brightness.dark - ? Icons.light_mode_outlined - : Icons.dark_mode_outlined, - ), - ), - ), - body: ListView.separated( - itemCount: pages.length, - itemBuilder: (_, index) => pages[index], - separatorBuilder: (_, __) => Divider( - color: theme.dividerColor, - ), - ), - ); - } -} - -class _ListItem extends StatelessWidget { - const _ListItem({ - required this.onTap, - required this.icon, - required this.title, - required this.subtitle, - }); - - final VoidCallback onTap; - final Icon icon; - final Text title; - final Text subtitle; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return ListTile( - leading: IconTheme( - data: IconThemeData( - color: theme.iconTheme.color, - ), - child: icon, - ), - title: title, - subtitle: subtitle, - trailing: const Icon(Icons.arrow_forward), - onTap: onTap, - iconColor: theme.colorScheme.onSurface, - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart b/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart deleted file mode 100644 index 00763fc..0000000 --- a/todo/packages/todo_ui/gallery/lib/spacing/spacing.dart +++ /dev/null @@ -1 +0,0 @@ -export 'spacing_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart b/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart deleted file mode 100644 index 476f5db..0000000 --- a/todo/packages/todo_ui/gallery/lib/spacing/spacing_page.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class SpacingPage extends StatelessWidget { - const SpacingPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const SpacingPage()); - } - - @override - Widget build(BuildContext context) { - const spacingList = [ - _SpacingItem(spacing: UISpacing.xxxs, name: 'xxxs'), - _SpacingItem(spacing: UISpacing.xxs, name: 'xxs'), - _SpacingItem(spacing: UISpacing.xs, name: 'xs'), - _SpacingItem(spacing: UISpacing.sm, name: 'sm'), - _SpacingItem(spacing: UISpacing.md, name: 'md'), - _SpacingItem(spacing: UISpacing.lg, name: 'lg'), - _SpacingItem(spacing: UISpacing.xlg, name: 'xlg'), - _SpacingItem(spacing: UISpacing.xxlg, name: 'xxlg'), - _SpacingItem(spacing: UISpacing.xxxlg, name: 'xxxlg'), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('Spacing')), - body: ListView.builder( - itemCount: spacingList.length, - itemBuilder: (_, index) => spacingList[index], - ), - ); - } -} - -class _SpacingItem extends StatelessWidget { - const _SpacingItem({required this.spacing, required this.name}); - - final double spacing; - final String name; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(UISpacing.sm), - child: Row( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - color: UIColors.black, - width: UISpacing.xxs, - height: UISpacing.lg, - ), - Container( - width: spacing, - height: UISpacing.lg, - color: UIColors.skyBlue, - ), - Container( - color: UIColors.black, - width: UISpacing.xxs, - height: UISpacing.lg, - ), - ], - ), - const SizedBox(width: UISpacing.sm), - Text(name), - ], - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/typography/typography.dart b/todo/packages/todo_ui/gallery/lib/typography/typography.dart deleted file mode 100644 index 770dff3..0000000 --- a/todo/packages/todo_ui/gallery/lib/typography/typography.dart +++ /dev/null @@ -1 +0,0 @@ -export 'typography_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart b/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart deleted file mode 100644 index 59281a9..0000000 --- a/todo/packages/todo_ui/gallery/lib/typography/typography_page.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class TypographyPage extends StatelessWidget { - const TypographyPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const TypographyPage()); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final uiTextStyleList = [ - _TextItem(name: 'Display Large', style: theme.textTheme.displayLarge!), - _TextItem(name: 'Display Medium', style: theme.textTheme.displayMedium!), - _TextItem(name: 'Display Small', style: theme.textTheme.displaySmall!), - _TextItem( - name: 'Headline Medium', - style: theme.textTheme.headlineMedium!, - ), - _TextItem(name: 'Headline Small', style: theme.textTheme.headlineSmall!), - _TextItem(name: 'Title large', style: theme.textTheme.titleLarge!), - _TextItem(name: 'Title medium', style: theme.textTheme.titleMedium!), - _TextItem(name: 'Title small', style: theme.textTheme.titleSmall!), - _TextItem(name: 'Body large', style: theme.textTheme.bodyLarge!), - _TextItem(name: 'Body medium', style: theme.textTheme.bodyMedium!), - _TextItem(name: 'Body small', style: theme.textTheme.bodySmall!), - _TextItem(name: 'Label large', style: theme.textTheme.labelLarge!), - _TextItem(name: 'Label Small', style: theme.textTheme.labelSmall!), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('Typography')), - body: ListView( - children: [ - const Center(child: Text('UI Typography Text styles')), - const SizedBox(height: 16), - ...uiTextStyleList, - ], - ), - ); - } -} - -class _TextItem extends StatelessWidget { - const _TextItem({required this.name, required this.style}); - - final String name; - final TextStyle style; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: UISpacing.sm, - vertical: UISpacing.lg, - ), - child: Text(name, style: style), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart deleted file mode 100644 index c9ff46c..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/app_bar_page.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class AppBarPage extends StatefulWidget { - const AppBarPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const AppBarPage()); - } - - @override - State createState() => _AppBarPage(); -} - -class _AppBarPage extends State { - @override - Widget build(BuildContext context) { - const contentSpacing = UISpacing.sm; - final appBarList = [ - const SizedBox(height: contentSpacing), - UIAppBar.redAppBar( - textTitle: 'Red AppBar', - ), - const SizedBox(height: contentSpacing), - UIAppBar.actionAppBar( - textTitle: 'Action AppBar', - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('App Assets')), - body: Align( - alignment: Alignment.topCenter, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: appBarList.map((appBar) { - return appBar; - }).toList(), - ), - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart deleted file mode 100644 index afa2f74..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/button_page.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class ButtonPage extends StatelessWidget { - const ButtonPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const ButtonPage()); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - const contentSpacing = UISpacing.sm; - final appButtonList = [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Icon button'), - IconButton( - onPressed: () {}, - color: theme.colorScheme.primary, - icon: const Icon(Icons.add), - ), - ], - ), - const SizedBox(height: contentSpacing), - OutlinedButton( - onPressed: () {}, - style: theme.outlinedButtonTheme.style, - child: Text( - 'Secondary button', - style: Theme.of(context).textTheme.labelLarge, - ), - ), - const SizedBox(height: contentSpacing), - OutlinedButton( - onPressed: () {}, - style: theme.outlinedButtonTheme.style, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - theme.icons.emailOutline( - color: UIColors.blue, - ), - const SizedBox(width: contentSpacing), - const Text('Primary button + logo'), - ], - ), - ), - const SizedBox(height: contentSpacing), - TextButton( - style: Theme.of(context).textButtonTheme.style, - onPressed: () {}, - child: const Text( - 'Text button', - textAlign: TextAlign.center, - ), - ), - const SizedBox(height: contentSpacing), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Floating action button'), - const SizedBox( - width: 10, - ), - FloatingActionButton( - onPressed: () {}, - backgroundColor: theme.colorScheme.surface, - child: theme.icons.emailOutline(size: 20), - ), - ], - ), - const SizedBox(height: contentSpacing), - Chip( - backgroundColor: theme.chipTheme.backgroundColor, - padding: EdgeInsets.zero, - label: const Padding( - padding: EdgeInsets.symmetric( - vertical: UISpacing.xxs, - horizontal: UISpacing.xs, - ), - child: Text( - 'Common chip', - ), - ), - ), - const SizedBox(height: contentSpacing), - ChoiceChip( - avatar: theme.icons.emailOutline( - color: UIColors.black, - ), - padding: const EdgeInsets.all(8), - label: Text( - 'Icon chip', - style: theme.chipTheme.labelStyle, - ), - selected: false, - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('App Buttons & Chips')), - body: Align( - alignment: Alignment.topCenter, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: appButtonList.map((button) { - return button; - }).toList(), - ), - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart deleted file mode 100644 index 59fddfc..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/icons_page.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class IconsPage extends StatefulWidget { - const IconsPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const IconsPage()); - } - - @override - State createState() => _IconsPage(); -} - -class _IconsPage extends State { - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - const contentSpacing = UISpacing.sm; - final appIconsList = [ - const SizedBox(height: contentSpacing), - theme.icons.emailOutline( - size: 30, - ), - const SizedBox(height: contentSpacing), - theme.icons.backIcon( - size: 30, - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('App Assets')), - body: Align( - alignment: Alignment.topCenter, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: appIconsList.map((icon) { - return icon; - }).toList(), - ), - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart deleted file mode 100644 index e5d79fe..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/text_field_page.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class TextFieldPage extends StatelessWidget { - const TextFieldPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const TextFieldPage()); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - 'Text Field', - ), - ), - body: Padding( - padding: const EdgeInsets.all(UISpacing.lg), - child: Column( - children: [ - UITextField.emailTextField( - onChanged: (_) {}, - ), - const SizedBox(height: UISpacing.sm), - UITextField.passwordTextField( - onChanged: (_) {}, - ), - const SizedBox(height: UISpacing.sm), - UITextField( - hintText: 'Default text field', - onChanged: (_) {}, - ), - ], - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart b/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart deleted file mode 100644 index 35b87fc..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/widgets.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'app_bar_page.dart'; -export 'button_page.dart'; -export 'icons_page.dart'; -export 'text_field_page.dart'; -export 'widgets_page.dart'; diff --git a/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart b/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart deleted file mode 100644 index 3e8debb..0000000 --- a/todo/packages/todo_ui/gallery/lib/widgets/widgets_page.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gallery/widgets/widgets.dart'; - -class WidgetsPage extends StatelessWidget { - const WidgetsPage({super.key}); - - static Route route() { - return MaterialPageRoute(builder: (_) => const WidgetsPage()); - } - - @override - Widget build(BuildContext context) { - final widgets = <_ListItem>[ - _ListItem( - icon: const Icon(Icons.vignette), - title: const Text('App Buttons'), - onTap: () => Navigator.of(context).push(ButtonPage.route()), - ), - _ListItem( - icon: const Icon(Icons.email_outlined), - title: const Text('Text Fields'), - onTap: () => Navigator.of(context).push(TextFieldPage.route()), - ), - _ListItem( - icon: const Icon(Icons.drag_indicator_outlined), - title: const Text('Icons'), - onTap: () => Navigator.of(context).push(IconsPage.route()), - ), - _ListItem( - icon: const Icon(Icons.drag_indicator_outlined), - title: const Text('Appbar'), - onTap: () => Navigator.of(context).push(AppBarPage.route()), - ), - ]; - - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar(title: const Text('Widgets')), - body: ListView.separated( - itemCount: widgets.length, - itemBuilder: (_, index) => widgets[index], - separatorBuilder: (_, __) => Divider( - color: theme.dividerColor, - ), - ), - ); - } -} - -class _ListItem extends StatelessWidget { - const _ListItem({ - required this.icon, - required this.title, - this.onTap, - }); - - final VoidCallback? onTap; - final Widget icon; - final Text title; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return ListTile( - leading: IconTheme( - data: IconThemeData( - color: theme.iconTheme.color, - ), - child: icon, - ), - title: title, - trailing: Icon( - Icons.chevron_right, - color: theme.colorScheme.onSurface, - ), - onTap: onTap, - iconColor: theme.iconTheme.color, - ); - } -} diff --git a/todo/packages/todo_ui/gallery/pubspec.yaml b/todo/packages/todo_ui/gallery/pubspec.yaml deleted file mode 100644 index 0783679..0000000 --- a/todo/packages/todo_ui/gallery/pubspec.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: gallery -description: Gallery project to showcase todo_ui -version: 0.0.1 -publish_to: none - -environment: - sdk: ">=2.18.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_asset_generator: ^1.0.2 - todo_ui: - path: ../ - -dev_dependencies: - very_good_analysis: ^5.1.0 - -flutter: - uses-material-design: true diff --git a/todo/packages/todo_ui/gallery/web/favicon.png b/todo/packages/todo_ui/gallery/web/favicon.png deleted file mode 100644 index 8aaa46ac1ae21512746f852a42ba87e4165dfdd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM diff --git a/todo/packages/todo_ui/gallery/web/icons/Icon-192.png b/todo/packages/todo_ui/gallery/web/icons/Icon-192.png deleted file mode 100644 index 510b28316f0e86cac194f79338c2f7e0eafaa101..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 840 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX1GXs1=T>t<7A9C~uWB6>%ZQu4k z`SarIzfV8^)t~=)^Zv`PKYvfU`YZR;&rqO((CPXq3+{dRn{eWX(T;Ctp8xSa_?fkNvpy?k}THhvTkq+S|V|*yXFOd1;)w&>&?2qh}L?UNEC;wc5JZTmi*5 z-~O@N`Auj0H=g)O+DY>m%#*kyni;gb-u?J@_Qjv1Bi|g>zckF5tsObx!M^3%3=B+J zo-U3d6}R48a^yM`z~g#R{|48qh`TP|_uD#$Pk5+pnJ%}ZWjoL_3=BXK7ioknxOzDf|9EJY=4Gv(sUn>{r*l!h5cNy=`k#^m}nb zh5g2cpMU*X@BGtvaMwTjz@LB1neysIKD=A5n^5;Zm~p#3SH<_sVjTDX|J1ztqMvEu z|BPu_U)&FVsC!^9l^fjqPh-L!`x#56{st<1c5ig67vXu&p5Q9=cXyLw{T+wJ_FO6d zG%md8Z+iH5i}od;{+af93ohQ@S-#Mo%jB2!M*FZMZ~LVSfDXAWul?xTao!z&0}s6I z7cKn#m<{NpfLH$+>p5@xPyKf;zlCx2`;Eb!{6_y{_k~9Vu+IEn@@dhQ1NkZcJd4(E zRXFkIt5`_l347~b%%?=xmZe^karn$zBk^Ir$|-KWbs2RL&kP%iT;2a0IUi8DSjQ}X z?w5F~9P<_NkcV^rZ{mIseX%C?c!MU>m-`3W>;H8x5_8%RfBTT;cS5|BC66??28AyYfT4GW&cfv;2ej>Akk8j$cbPHto~6 z+jY$TV!Z diff --git a/todo/packages/todo_ui/gallery/web/icons/Icon-512.png b/todo/packages/todo_ui/gallery/web/icons/Icon-512.png deleted file mode 100644 index cf2fca6c86d486e344a26c045e7a34eae28097ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2172 zcmd^>ZBSEJ8pqGQ2_%q}grY#JA-HN4CDn!0yp=ZUfNHc&a~ax{nlsB zoag!ff6p^>=FItUj>N@8E^~gu833?sbJV8o0N`wi13rgM?Ps6tVpDNkbbJItDE>(*46Kg= z{)v)`9&yPPud?>P4blH<={<1%mI~Eq0N~Ya-V_#}JFNfueJ64?0AYpr8OEBy4=bXQ zCLGx#|5|9yEldXlbsbi>R2@>fpbU7|-nfu8JAR+)B~GD$|Eqz%h~zC@Dk!jD;59El zIE(J1UUUitqAWOWrxu9ck#$iBEm!sM%CZfocTC4K+8z|ibNIVd9R7`V_SDM ze6V9^qFM}p++HY0bdT1zhOFu$9zVT*N&;;I6XU^lle(RH$97LqOz4cAlnc5wQBH_q zWxE7DZ%@ie2)It=n799#uugG|2E~2=okl|eD$Nv=#Fhb}Mpp9{Pg{Klag2Ra2FzA7 zPXqhx;(Gudxxo5ER@SnxL(O?!B$2%V4t3koeC}0OmJ$bkS?pmHeOX&qz$Eb`DTX>T zus3ehV2A8I93xfpkf%3Eg&HEp?1xyA<}EZY$z-$W2lp08O&gM>a@>0JP98 zoM&ys@7}`&Ml(!zp4BD9HGW$6P}k9HWWg^9QNnlq``vrlGw zD6b}81J4EvQjqm_&$0F034o?r4q}Pf?i$$nrJ(;WUjU23C7C>4lo*6K{erj~4y6WS zcu~zU;_tqV71CKkRR%~V>w@|o5fakF*jY6?9pKDK^*nxfL)r?J>t3hC)k7Boz8ORF z|2|n!umWzc?I@+J?yZrRT_ekEX_gZLdA3OTpu{?g;wzd%Kr4`I>er3EM<5V=3VU~sRzje*p{_p8E6I*0g^ludF>vYcVqG4j;ng&+*kR?3vgFaV(!Sra-PN?@Z&@# zFS^G;i*fN)-1L&%S!f2H7{H!hJ9Y^*)L!9e1%O-oW+8d^P!6{DpL{+hC(BA2^@?pA z5_r32-6fm>H~nD}o~c_y?&Gq(M{G1ruT=3whaKf)RkFMCs*RCh-@1X-@|UH~wwzBm zDo$K6t5Q4EdStZ+f#<X~3Kvo&cgK~X z{C;=SImW;v!2>m^^_Tyr7<}Za9%8wR=5~%YFQ4E4gy}29FkVZnN zy2(!ad6@KYt*Z9B$zpKKHFic?neTmzhrPb8Y7DhRR|@gy$Pg1ezriPrG?hfQqN8n# zqLh$HC3@%O;_`gHpO+~G?SS@bAzm73Z$=H#<2Ul>mUK8kFFr1D6DJIBELyKq|raOTO)^7h*TwN(_{6J%#x0-?l;rKM%)2iYJ+x<}<6# z@qOvj^r%W~zvG?9(1WN6Yozw|aB4|gt9tBL#rw@yo0rEHFBBIQ zKk(cGL1qYN+ZMs8p7A@~0lh4HcHtL~?!cp^lsk>#Ldv;EVeY*^vM>BuvU61UFKHHq zUT^%L+UyhLCa4yaZntgl{#vm&>M}we4~qIXrCF}4TomE?WNW&Qg+tKxilEijJ>MSm z{5!sp*dz6rPcHp_n*DTfBU640N{0D7lN?~tEg7fS<7~v-4Hwg|E;2)UeWV)UZKyz^ zGw18oc~Aj-)-07Qw5lD41zzuaHkNmj?e>f&kYFq3o5mtkTHd>^;` zwIDJDq1V~i_vGtGcq)m(b4fbcKy!nPJP#*({Tv&L69$=S`VAzOk}d=TcJdPxbGia^?Z=qCm{xt_Ps%kJ%w=^6S14NQ z$vHID3F4%w!=#Uu#`u5|+R6Nhj5Dny_c9bVhFHh8nN9`CHFc9zGh<9eAo0FB+e$>SfTB_K!3^le?sp8xy;T`;$sE={oEDBO|Y4?y961HPTwy zETAh$%S=`X9wuE{XNOWM6M8}PhRh=8NDaMhrk9*jek08k9hl62wY*fBc2%w*GcsAI zc-pev)R=t~AMy8RC)0_T3Y*z>S9?>85u^yXxR8xb5QO9W@;i>XN$9#d25ox~&sqHD^j? z`L+9*fyviY{fQ4L!l`INVRZuQvO06qs%D5c?oQX}WllNAJ}xN7Q4SKJ-OD%w&ueEU iM9jZ%HFXYXyU%f`ZK8t=g|G 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 diff --git a/todo/packages/todo_ui/gallery/web/index.html b/todo/packages/todo_ui/gallery/web/index.html deleted file mode 100644 index cfd3b6a..0000000 --- a/todo/packages/todo_ui/gallery/web/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - gallery - - - - - - - diff --git a/todo/packages/todo_ui/gallery/web/manifest.json b/todo/packages/todo_ui/gallery/web/manifest.json deleted file mode 100644 index cf706d7..0000000 --- a/todo/packages/todo_ui/gallery/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "gallery", - "short_name": "gallery", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} diff --git a/todo/packages/todo_ui/lib/src/colors/todo_colors.dart b/todo/packages/todo_ui/lib/src/colors/todo_colors.dart deleted file mode 100644 index 2515e12..0000000 --- a/todo/packages/todo_ui/lib/src/colors/todo_colors.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/material.dart'; - -/// A class which contains all the colors used in the app. -abstract class UIColors { - /// Black - static const Color black = Color(0xFF000000); - - /// White - static const Color white = Color(0xFFFFFFFF); - - /// Transparent - static const Color transparent = Color(0x00000000); - - /// The grey primary color and swatch. - static const Color grey = Colors.grey; - - /// The light grey color. - static const Color lightGrey = Color(0xFFEEEEEE); - - /// The liver color. - static const Color liver = Color(0xFF4D4D4D); - - /// The dark aqua color. - static const Color darkAqua = Color(0xFF00677F); - - /// The blue primary color and swatch. - static const Color blue = Color(0xFF3898EC); - - /// The sky blue color. - static const Color skyBlue = Color(0xFF0175C2); - - /// The ocean blue color. - static const Color oceanBlue = Color(0xFF02569B); - - /// The light blue accent color. - static const Color lightBlue = Color(0xFFB3E5FC); - - /// The light blue accent color. - static const Color lightBlue200 = Color(0xFFB3E5FC); - - /// The crystal blue color. - static const Color crystalBlue = Color(0xFF55ACEE); - - /// The light surface dress color. - static const Color surface = Color(0xFFEBF2F7); - - /// The pale sky color. - static const Color paleSky = Color(0xFF73777F); - - /// The bright grey color. - static const Color brightGrey = Color(0xFFEAEAEA); - - /// The red primary color and swatch. - static const MaterialColor red = Colors.red; - - /// The on-background color. - static const Color onBackground = Color(0xFF1A1A1A); - - /// The primary container color. - static const Color primaryContainer = Color(0xFFB1EBFF); - - /// The red wine color. - static const Color redWine = Color(0xFF9A031E); - - /// The modal background color. - static const Color modalBackground = Color(0xFFEBF2F7); - - /// The medium emphasis primary color. - static const Color mediumEmphasisPrimary = Color(0xBDFFFFFF); - - /// The medium emphasis surface color. - static const Color mediumEmphasisSurface = Color(0x99000000); - - /// The border outline color. - static const Color borderOutline = Color(0x33000000); - - /// The light outline color. - static const Color outlineLight = Color(0x33000000); - - /// The secondary color of application. - static const MaterialColor secondary = MaterialColor(0xFF963F6E, { - 50: Color(0xFFFFECF3), - 100: Color(0xFFFFD8E9), - 200: Color(0xFFFFAFD6), - 300: Color(0xFFF28ABE), - 400: Color(0xFFD371A3), - 500: Color(0xFFB55788), - 600: Color(0xFF963F6E), - 700: Color(0xFF7A2756), - 800: Color(0xFF5F0F40), - 900: Color(0xFF3D0026), - }); -} diff --git a/todo/packages/todo_ui/lib/src/extensions/extensions.dart b/todo/packages/todo_ui/lib/src/extensions/extensions.dart deleted file mode 100644 index 58d9d63..0000000 --- a/todo/packages/todo_ui/lib/src/extensions/extensions.dart +++ /dev/null @@ -1 +0,0 @@ -export 'theme_data.dart'; diff --git a/todo/packages/todo_ui/lib/src/extensions/theme_data.dart b/todo/packages/todo_ui/lib/src/extensions/theme_data.dart deleted file mode 100644 index 2f45291..0000000 --- a/todo/packages/todo_ui/lib/src/extensions/theme_data.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -///Extension on Themedata that must be added to return the correct asset -extension ThemeX on ThemeData { - ///Returns the correct UIIcon based on the current theme - UIIcon get icons { - final isDarkMode = brightness == Brightness.dark; - if (isDarkMode) { - return UIIconsDark(); - } else { - return UIIconsLight(); - } - } -} diff --git a/todo/packages/todo_ui/lib/src/icons/icons.dart b/todo/packages/todo_ui/lib/src/icons/icons.dart deleted file mode 100644 index cfe92d4..0000000 --- a/todo/packages/todo_ui/lib/src/icons/icons.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'todo_icons.dart'; -export 'todo_icons_dark.dart'; -export 'todo_icons_light.dart'; diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons.dart deleted file mode 100644 index d21001e..0000000 --- a/todo/packages/todo_ui/lib/src/icons/todo_icons.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:todo_ui/gen/assets.gen.dart'; - -/// A class which contains all the icons used in the app -abstract class UIIcon { - final _package = 'todo_ui'; - - ///Returns the back icon - SvgPicture backIcon({double? size, Color? color}) { - return Assets.icons.backIcon.svg( - height: size ?? 14, - width: size ?? 14, - package: _package, - ); - } - - ///Returns the email outline iconc - SvgPicture emailOutline({double? size, Color? color}) { - return Assets.icons.emailOutline.svg( - height: size ?? 14, - width: size ?? 14, - package: _package, - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart deleted file mode 100644 index 3b5a0e0..0000000 --- a/todo/packages/todo_ui/lib/src/icons/todo_icons_dark.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// Icons to be used with dark theme. -class UIIconsDark extends UIIcon { - @override - SvgPicture backIcon({double? size, Color? color}) { - return super.backIcon( - color: color ?? UIColors.white, - size: size ?? 14, - ); - } - - @override - SvgPicture emailOutline({double? size, Color? color}) { - return super.emailOutline( - color: color ?? UIColors.white, - size: size ?? 14, - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart b/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart deleted file mode 100644 index e4311c3..0000000 --- a/todo/packages/todo_ui/lib/src/icons/todo_icons_light.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// Icons to be used with light theme. -class UIIconsLight extends UIIcon { - @override - SvgPicture backIcon({double? size, Color? color}) { - return super.backIcon( - color: color ?? UIColors.black, - size: size ?? 14, - ); - } - - @override - SvgPicture emailOutline({double? size, Color? color}) { - return super.emailOutline( - color: color ?? UIColors.black, - size: size ?? 14, - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart b/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart deleted file mode 100644 index d1949aa..0000000 --- a/todo/packages/todo_ui/lib/src/spacing/todo_spacing.dart +++ /dev/null @@ -1,38 +0,0 @@ -/// A class that contains the spacing values used in the app. -abstract class UISpacing { - /// The default unit of spacing - static const double spaceUnit = 16; - - /// xxxs spacing value (1pt) - static const double xxxs = 0.0625 * spaceUnit; - - /// xxs spacing value (2pt) - static const double xxs = 0.125 * spaceUnit; - - /// xs spacing value (4pt) - static const double xs = 0.25 * spaceUnit; - - /// sm spacing value (8pt) - static const double sm = 0.5 * spaceUnit; - - /// md spacing value (12pt) - static const double md = 0.75 * spaceUnit; - - /// lg spacing value (16pt) - static const double lg = spaceUnit; - - /// xlg spacing value (24pt) - static const double xlg = 1.5 * spaceUnit; - - /// xxlg spacing value (40pt) - static const double xxlg = 2.5 * spaceUnit; - - /// xxxlg spacing value (64pt) - static const double xxxlg = 4 * spaceUnit; - - /// xxxlg spacing value (88pt) - static const double xxxxlg = 5.5 * spaceUnit; - - /// s10 - static const double s10 = 10; -} diff --git a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart b/todo/packages/todo_ui/lib/src/theme/todo_theme.dart deleted file mode 100644 index 1488619..0000000 --- a/todo/packages/todo_ui/lib/src/theme/todo_theme.dart +++ /dev/null @@ -1,426 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// A class that provides the theme for the app. -class UITheme { - - /// Light Theme example based on Material 2 Design. - ThemeData get lightTheme { - return ThemeData( - primaryColor: UIColors.skyBlue, - canvasColor: _backgroundColor, - scaffoldBackgroundColor: _backgroundColor, - iconTheme: _lightIconTheme, - appBarTheme: _lightAppBarTheme, - dividerTheme: _dividerTheme, - textTheme: _lightTextTheme, - buttonTheme: _buttonTheme, - splashColor: UIColors.transparent, - elevatedButtonTheme: _elevatedButtonTheme, - textButtonTheme: _textButtonTheme, - colorScheme: _lightColorScheme, - bottomSheetTheme: _lightBottomSheetTheme, - listTileTheme: _listTileTheme, - switchTheme: _switchTheme, - progressIndicatorTheme: _progressIndicatorTheme, - tabBarTheme: _tabBarTheme, - bottomNavigationBarTheme: _bottomAppBarTheme, - chipTheme: _chipTheme, - dividerColor: UIColors.grey, - outlinedButtonTheme: _lightOutlinedButtonTheme, - ); - } - - /// Dark Theme example based on Material 2 Design. - ThemeData get darkTheme => lightTheme.copyWith( - chipTheme: _darkChipTheme, - brightness: Brightness.dark, - scaffoldBackgroundColor: UIColors.black, - colorScheme: _darkColorScheme, - appBarTheme: _darkAppBarTheme, - disabledColor: UIColors.white.withValues(alpha: 0.5), - textTheme: _darkTextTheme, - unselectedWidgetColor: UIColors.lightGrey, - iconTheme: _darkIconTheme, - bottomSheetTheme: _darkBottomSheetTheme, - outlinedButtonTheme: _darkOutlinedButtonTheme, - textButtonTheme: _darkTextButtonTheme, - ); - - /// Returns the correct color based on the current theme. - ColorScheme get _lightColorScheme { - return ColorScheme.light( - primary: UIColors.skyBlue, - primaryContainer: UIColors.lightBlue200, - onPrimaryContainer: UIColors.oceanBlue, - secondary: UIColors.paleSky, - onSecondary: UIColors.white, - secondaryContainer: UIColors.lightBlue200, - onSecondaryContainer: UIColors.oceanBlue, - tertiary: UIColors.secondary.shade700, - onTertiary: UIColors.white, - tertiaryContainer: UIColors.secondary.shade300, - onTertiaryContainer: UIColors.secondary.shade700, - error: UIColors.red, - errorContainer: UIColors.red.shade200, - onErrorContainer: UIColors.redWine, - surface: _backgroundColor, - onSurface: UIColors.onBackground, - surfaceContainerHighest: UIColors.surface, - onSurfaceVariant: UIColors.grey, - inversePrimary: UIColors.crystalBlue, - ); - } - - /// Returns the correct color based on the current theme. - ColorScheme get _darkColorScheme => _lightColorScheme.copyWith( - surface: UIColors.black, - onSurface: UIColors.lightGrey, - primary: UIColors.blue, - onPrimary: UIColors.oceanBlue, - primaryContainer: UIColors.oceanBlue, - onPrimaryContainer: UIColors.lightBlue200, - secondary: UIColors.paleSky, - onSecondary: UIColors.lightGrey, - secondaryContainer: UIColors.liver, - onSecondaryContainer: UIColors.brightGrey, - ); - - /// Returns the correct background color based on the current theme. - Color get _backgroundColor => UIColors.white; - - /// Returns the correct [AppBarTheme] based on the current theme. - AppBarTheme get _lightAppBarTheme { - return AppBarTheme( - iconTheme: _lightIconTheme, - titleTextStyle: _lightTextTheme.titleMedium, - elevation: 0, - toolbarHeight: 64, - backgroundColor: UIColors.white, - toolbarTextStyle: _lightTextTheme.titleLarge, - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarIconBrightness: Brightness.dark, - statusBarBrightness: Brightness.light, - ), - ); - } - - /// Returns the correct [AppBarTheme] based on the current theme. - AppBarTheme get _darkAppBarTheme { - return _lightAppBarTheme.copyWith( - backgroundColor: UIColors.grey, - iconTheme: _darkIconTheme, - toolbarTextStyle: _darkTextTheme.titleLarge, - titleTextStyle: _darkTextTheme.titleMedium, - ); - } - - /// Returns the correct [IconThemeData] based on the current theme. - IconThemeData get _lightIconTheme { - return const IconThemeData( - color: UIColors.black, - ); - } - - /// Returns the correct [IconThemeData] based on the current theme. - IconThemeData get _darkIconTheme { - return const IconThemeData( - color: UIColors.white, - ); - } - - /// Returns the correct [DividerThemeData] based on the current theme. - DividerThemeData get _dividerTheme { - return const DividerThemeData( - color: UIColors.outlineLight, - space: UISpacing.lg, - thickness: UISpacing.xxxs, - indent: UISpacing.lg, - endIndent: UISpacing.lg, - ); - } - - /// Returns the correct [TextTheme] based on the current theme. - TextTheme get _lightTextTheme => lightUITextTheme; - - /// Returns the correct [TextTheme] based on the current theme. - TextTheme get _darkTextTheme { - return _lightTextTheme.apply( - bodyColor: UIColors.white, - displayColor: UIColors.white, - decorationColor: UIColors.white, - ); - } - - /// The UI text theme based on [UITextStyle]. - static final lightUITextTheme = TextTheme( - displayLarge: UITextStyle.headline1, - displayMedium: UITextStyle.headline2, - displaySmall: UITextStyle.headline3, - titleMedium: UITextStyle.subtitle1, - titleSmall: UITextStyle.subtitle2, - bodyLarge: UITextStyle.bodyText1, - bodyMedium: UITextStyle.bodyText2, - labelLarge: UITextStyle.button, - bodySmall: UITextStyle.caption, - labelSmall: UITextStyle.overline, - ).apply( - bodyColor: UIColors.black, - displayColor: UIColors.black, - decorationColor: UIColors.black, - ); - - /// Returns the correct [ChipThemeData] based on the current theme. - ChipThemeData get _chipTheme { - return ChipThemeData( - backgroundColor: UIColors.secondary.shade300, - disabledColor: _backgroundColor, - selectedColor: UIColors.secondary.shade700, - secondarySelectedColor: UIColors.secondary.shade700, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - labelStyle: UITextStyle.button.copyWith(color: UIColors.black), - secondaryLabelStyle: - UITextStyle.labelSmall.copyWith(color: UIColors.white), - brightness: Brightness.light, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(22), - side: const BorderSide(), - ), - ); - } - - /// Returns the correct [ChipThemeData] based on the current theme. - ChipThemeData get _darkChipTheme { - return _chipTheme.copyWith( - backgroundColor: UIColors.white, - disabledColor: _backgroundColor, - selectedColor: UIColors.secondary.shade700, - secondarySelectedColor: UIColors.secondary.shade700, - labelStyle: UITextStyle.button.copyWith( - color: UIColors.secondary.shade700, - ), - secondaryLabelStyle: UITextStyle.labelSmall.copyWith( - color: UIColors.black, - ), - shape: RoundedRectangleBorder( - side: const BorderSide(color: UIColors.white, width: 2), - borderRadius: BorderRadius.circular(22), - ), - ); - } - - /// Returns the correct [ButtonThemeData] based on the current theme. - ButtonThemeData get _buttonTheme { - return ButtonThemeData( - textTheme: ButtonTextTheme.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(UISpacing.sm), - ), - padding: const EdgeInsets.symmetric(vertical: UISpacing.lg), - buttonColor: UIColors.blue, - disabledColor: UIColors.lightGrey, - height: 48, - minWidth: 48, - ); - } - - /// Returns the correct [ElevatedButtonThemeData] based on the current theme. - ElevatedButtonThemeData get _elevatedButtonTheme { - return ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(30)), - ), - padding: const EdgeInsets.symmetric(vertical: UISpacing.lg), - textStyle: _lightTextTheme.labelLarge?.copyWith( - fontWeight: UIFontWeight.bold, - ), - backgroundColor: UIColors.blue, - ), - ); - } - - /// Returns the correct [TextButtonThemeData] based on the current theme. - TextButtonThemeData get _textButtonTheme { - return TextButtonThemeData( - style: TextButton.styleFrom( - textStyle: _lightTextTheme.labelLarge?.copyWith( - fontWeight: UIFontWeight.light, - ), - foregroundColor: UIColors.black, - ), - ); - } - - /// Returns the correct [TextButtonThemeData] based on the current theme. - TextButtonThemeData get _darkTextButtonTheme { - return TextButtonThemeData( - style: TextButton.styleFrom( - textStyle: _lightTextTheme.labelLarge?.copyWith( - fontWeight: UIFontWeight.light, - ), - foregroundColor: UIColors.white, - ), - ); - } - - /// Returns the correct [BottomSheetThemeData] based on the current theme. - BottomSheetThemeData get _lightBottomSheetTheme { - return const BottomSheetThemeData( - backgroundColor: UIColors.modalBackground, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(UISpacing.lg), - topRight: Radius.circular(UISpacing.lg), - ), - ), - ); - } - - /// Returns the correct [BottomSheetThemeData] based on the current theme. - BottomSheetThemeData get _darkBottomSheetTheme { - return _lightBottomSheetTheme.copyWith( - backgroundColor: UIColors.grey, - ); - } - - /// Returns the correct [ListTileThemeData] based on the current theme. - ListTileThemeData get _listTileTheme { - return const ListTileThemeData( - iconColor: UIColors.onBackground, - contentPadding: EdgeInsets.all(UISpacing.lg), - ); - } - - /// Returns the correct [SwitchThemeData] based on the current theme. - SwitchThemeData get _switchTheme { - return SwitchThemeData( - thumbColor: - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.selected)) { - return UIColors.darkAqua; - } - return UIColors.black; - }), - trackColor: - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.selected)) { - return UIColors.primaryContainer; - } - return UIColors.paleSky; - }), - ); - } - - /// Returns the correct [ProgressIndicatorThemeData] based on the current - /// theme. - ProgressIndicatorThemeData get _progressIndicatorTheme { - return const ProgressIndicatorThemeData( - color: UIColors.darkAqua, - circularTrackColor: UIColors.borderOutline, - ); - } - - /// Returns the correct [TabBarTheme] based on the current theme. - TabBarThemeData get _tabBarTheme { - return TabBarThemeData( - labelStyle: UITextStyle.button, - labelColor: UIColors.darkAqua, - labelPadding: const EdgeInsets.symmetric( - horizontal: UISpacing.lg, - vertical: UISpacing.md + UISpacing.xxs, - ), - unselectedLabelStyle: UITextStyle.button, - unselectedLabelColor: UIColors.mediumEmphasisSurface, - indicator: const UnderlineTabIndicator( - borderSide: BorderSide( - width: 3, - color: UIColors.darkAqua, - ), - ), - indicatorSize: TabBarIndicatorSize.label, - ); - } - - /// Returns the correct [BottomNavigationBarThemeData] based on the current - /// theme. - BottomNavigationBarThemeData get _bottomAppBarTheme { - return BottomNavigationBarThemeData( - backgroundColor: UIColors.black, - selectedItemColor: UIColors.white, - unselectedItemColor: UIColors.white.withValues(alpha: 0.74), - ); - } - - /// Returns the correct [OutlinedButtonThemeData] based on the current theme. - OutlinedButtonThemeData get _lightOutlinedButtonTheme { - return OutlinedButtonThemeData( - style: ButtonStyle( - shape: WidgetStateProperty.all(const StadiumBorder()), - backgroundColor: WidgetStateColor.resolveWith( - (states) => UIColors.white, - ), - side: WidgetStateProperty.resolveWith( - (states) => const BorderSide(), - ), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric( - horizontal: UISpacing.xlg, - vertical: UISpacing.lg, - ), - ), - alignment: Alignment.center, - textStyle: WidgetStateProperty.resolveWith( - (states) => states.contains(WidgetState.disabled) - ? UITextStyle.button.copyWith( - color: UIColors.black, - fontWeight: FontWeight.w500, - ) - : UITextStyle.button.copyWith( - color: states.contains(WidgetState.disabled) - ? UIColors.black - : UIColors.white, - fontWeight: FontWeight.w500, - ), - ), - ), - ); - } - - /// Returns the correct [OutlinedButtonThemeData] based on the current theme. - OutlinedButtonThemeData get _darkOutlinedButtonTheme { - return OutlinedButtonThemeData( - style: ButtonStyle( - shape: WidgetStateProperty.all(const StadiumBorder()), - backgroundColor: WidgetStateColor.resolveWith( - (states) => UIColors.black, - ), - side: WidgetStateProperty.resolveWith( - (states) => const BorderSide(color: UIColors.white), - ), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric( - horizontal: UISpacing.xlg, - vertical: UISpacing.lg, - ), - ), - alignment: Alignment.center, - textStyle: WidgetStateProperty.resolveWith( - (states) => states.contains(WidgetState.disabled) - ? UITextStyle.button.copyWith( - color: UIColors.white, - fontWeight: FontWeight.w500, - ) - : UITextStyle.button.copyWith( - color: states.contains(WidgetState.disabled) - ? UIColors.black - : UIColors.white, - fontWeight: FontWeight.w500, - ), - ), - ), - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart b/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart deleted file mode 100644 index 5f73655..0000000 --- a/todo/packages/todo_ui/lib/src/typography/todo_font_weight.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/widgets.dart'; - -/// Namespace for Default App Font Weights -abstract class UIFontWeight { - /// FontWeight value of `w900` - static const FontWeight black = FontWeight.w900; - - /// FontWeight value of `w800` - static const FontWeight extraBold = FontWeight.w800; - - /// FontWeight value of `w700` - static const FontWeight bold = FontWeight.w700; - - /// FontWeight value of `w600` - static const FontWeight semiBold = FontWeight.w600; - - /// FontWeight value of `w500` - static const FontWeight medium = FontWeight.w500; - - /// FontWeight value of `w400` - static const FontWeight regular = FontWeight.w400; - - /// FontWeight value of `w300` - static const FontWeight light = FontWeight.w300; - - /// FontWeight value of `w200` - static const FontWeight extraLight = FontWeight.w200; - - /// FontWeight value of `w100` - static const FontWeight thin = FontWeight.w100; -} diff --git a/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart b/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart deleted file mode 100644 index 3b5bd1c..0000000 --- a/todo/packages/todo_ui/lib/src/typography/todo_text_styles.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// TextStyles for the app -abstract class UITextStyle { - /// Base Text Style - static const _baseTextStyle = TextStyle( - package: 'todo_ui', - fontWeight: UIFontWeight.regular, - fontFamily: 'NotoSansDisplay', - decoration: TextDecoration.none, - textBaseline: TextBaseline.alphabetic, - ); - - /// Display 2 Text Style - static final TextStyle display2 = _baseTextStyle.copyWith( - fontSize: 57, - fontWeight: UIFontWeight.bold, - height: 1.12, - letterSpacing: -0.25, - ); - - /// Display 3 Text Style - static final TextStyle display3 = _baseTextStyle.copyWith( - fontSize: 45, - fontWeight: UIFontWeight.bold, - height: 1.15, - ); - - /// Headline 1 Text Style - static final TextStyle headline1 = _baseTextStyle.copyWith( - fontSize: 36, - fontWeight: UIFontWeight.bold, - height: 1.22, - ); - - /// Headline 2 Text Style - static final TextStyle headline2 = _baseTextStyle.copyWith( - fontSize: 32, - fontWeight: UIFontWeight.bold, - height: 1.25, - ); - - /// Headline 3 Text Style - static final TextStyle headline3 = _baseTextStyle.copyWith( - fontSize: 28, - fontWeight: UIFontWeight.semiBold, - height: 1.28, - ); - - /// Subtitle 1 Text Style - static final TextStyle subtitle1 = _baseTextStyle.copyWith( - fontSize: 16, - height: 1.5, - letterSpacing: 0.1, - ); - - /// Subtitle 2 Text Style - static final TextStyle subtitle2 = _baseTextStyle.copyWith( - fontSize: 14, - height: 1.42, - letterSpacing: 0.1, - ); - - /// Body Text 1 Text Style - static final TextStyle bodyText1 = _baseTextStyle.copyWith( - fontSize: 16, - height: 1.5, - letterSpacing: 0.5, - ); - - /// Body Text 2 Text Style (the default) - static final TextStyle bodyText2 = _baseTextStyle.copyWith( - fontSize: 14, - height: 1.42, - letterSpacing: 0.25, - ); - - /// Caption Text Style - static final TextStyle caption = _baseTextStyle.copyWith( - fontSize: 12, - height: 1.33, - letterSpacing: 0.4, - ); - - /// Button Text Style - static final TextStyle button = _baseTextStyle.copyWith( - fontSize: 16, - height: 1.42, - letterSpacing: 0.1, - ); - - /// Overline Text Style - static final TextStyle overline = _baseTextStyle.copyWith( - fontSize: 12, - height: 1.33, - letterSpacing: 0.5, - ); - - /// Label Small Text Style - static final TextStyle labelSmall = _baseTextStyle.copyWith( - fontSize: 11, - height: 1.45, - letterSpacing: 0.5, - ); -} diff --git a/todo/packages/todo_ui/lib/src/typography/typography.dart b/todo/packages/todo_ui/lib/src/typography/typography.dart deleted file mode 100644 index 732bcc0..0000000 --- a/todo/packages/todo_ui/lib/src/typography/typography.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'todo_font_weight.dart'; -export 'todo_text_styles.dart'; diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart b/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart deleted file mode 100644 index 0121fdd..0000000 --- a/todo/packages/todo_ui/lib/src/widgets/todo_app_bar.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// {@template app_app_bar} -/// A custom app bar widget. -/// {@endtemplate} -class UIAppBar extends StatelessWidget implements PreferredSizeWidget { - /// {@macro app_app_bar} - const UIAppBar({ - super.key, - this.widgetTitle, - this.textTitle, - this.titleTextStyle, - this.leading, - this.backgroundColor, - this.elevation, - this.centerTitle, - this.toolbarHeight, - this.leadingWidth, - this.actions, - this.buttonStyle, - this.automaticallyImplyLeading, - }); - - /// The AppBar if the user wants to add actions. - UIAppBar.actionAppBar({ - super.key, - this.textTitle, - this.titleTextStyle, - this.leading, - this.backgroundColor, - this.centerTitle, - this.leadingWidth, - this.buttonStyle, - }) : toolbarHeight = 50, - actions = [ - TextButton( - style: buttonStyle, - onPressed: () {}, - child: const Text('Action 1'), - ), - ], - elevation = 3, - widgetTitle = Text(textTitle!), - automaticallyImplyLeading = false; - - /// Red color AppBar. - UIAppBar.redAppBar({ - super.key, - this.textTitle, - this.titleTextStyle, - this.leading, - this.actions, - this.buttonStyle, - this.backgroundColor = UIColors.red, - this.automaticallyImplyLeading, - }) : elevation = 5, - widgetTitle = Text(textTitle!), - centerTitle = true, - toolbarHeight = 50, - leadingWidth = 60; - - /// The text for the title of the app bar is a Text. - final String? textTitle; - - /// The title of the app bar. - final Widget? widgetTitle; - - /// The style of the title text. - final TextStyle? titleTextStyle; - - /// The leading widget of the app bar. - final Widget? leading; - - /// The background color of the app bar. - final Color? backgroundColor; - - /// The elevation of the app bar. - final double? elevation; - - /// Whether the title should be centered. - final bool? centerTitle; - - /// The height of the app bar. - final double? toolbarHeight; - - /// The width of the leading widget. - final double? leadingWidth; - - /// The actions of the app bar. - final List? actions; - - /// The style of the button of actions from the appbar. - final ButtonStyle? buttonStyle; - - /// Whether the leading widget should be automatically implied if set to false - /// the automatic leading widget will not be shown. - final bool? automaticallyImplyLeading; - - @override - Widget build(BuildContext context) { - return AppBar( - title: widgetTitle, - titleTextStyle: - titleTextStyle ?? Theme.of(context).appBarTheme.titleTextStyle, - leading: leading, - backgroundColor: - backgroundColor ?? Theme.of(context).appBarTheme.backgroundColor, - elevation: elevation, - centerTitle: centerTitle, - toolbarHeight: toolbarHeight, - leadingWidth: leadingWidth, - actions: actions, - automaticallyImplyLeading: automaticallyImplyLeading ?? true, - ); - } - - @override - Size get preferredSize => Size.fromHeight(toolbarHeight ?? kToolbarHeight); -} diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart b/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart deleted file mode 100644 index ca71171..0000000 --- a/todo/packages/todo_ui/lib/src/widgets/todo_outlined_button.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// {@template outlined_button} -/// A button component based on material [OutlinedButton] widget -/// {@endtemplate} -class UIOutlinedButton extends StatelessWidget { - /// {@macro outlined_button} - const UIOutlinedButton({ - required this.text, - super.key, - this.backgroundColor, - this.darkBackgroundColor, - this.textColor, - this.onPressed, - this.borderColor, - this.icon, - }); - - /// Primary button which background color is the primary color of the theme - const UIOutlinedButton.primary({ - required this.text, - super.key, - this.onPressed, - this.icon, - }) : backgroundColor = UIColors.lightBlue200, - darkBackgroundColor = UIColors.blue, - borderColor = UIColors.white, - textColor = UIColors.black; - - /// Secondary button which background color - /// is the secondary color of the theme - const UIOutlinedButton.secondary({ - required this.text, - super.key, - this.onPressed, - this.icon, - }) : backgroundColor = UIColors.grey, - darkBackgroundColor = UIColors.grey, - borderColor = UIColors.white, - textColor = UIColors.white; - - /// Text on the button - final String text; - - /// Callback when the button is pressed - final VoidCallback? onPressed; - - /// Background color of the button - final Color? backgroundColor; - - /// Background color of the button in dark mode - final Color? darkBackgroundColor; - - /// Text color of the button - final Color? textColor; - - /// Border color of the button - final Color? borderColor; - - /// Icon on the button - final Widget? icon; - - @override - Widget build(BuildContext context) { - final isInactive = onPressed == null; - final theme = Theme.of(context); - return OutlinedButton( - onPressed: onPressed, - style: OutlinedButton.styleFrom( - shape: const StadiumBorder(), - disabledBackgroundColor: theme.colorScheme.surface, - backgroundColor: backgroundColor, - side: borderColor == null - ? null - : BorderSide( - color: onPressed == null - ? (theme.colorScheme.onSurface) - : borderColor!, - ), - padding: const EdgeInsets.symmetric( - horizontal: UISpacing.md, - vertical: UISpacing.md, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) - Padding( - padding: const EdgeInsets.only(right: 10), - child: icon, - ), - SizedBox( - child: Text( - text, - textAlign: TextAlign.center, - style: UITextStyle.button.copyWith( - color: - isInactive ? (theme.colorScheme.onSurface) : textColor, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart b/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart deleted file mode 100644 index 3ece3ff..0000000 --- a/todo/packages/todo_ui/lib/src/widgets/todo_text_field.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:todo_ui/todo_ui.dart'; - -/// {@template text_field} -/// A text field component based on material [TextFormField] widget which -/// extends TextField with email icon and validates input type. -/// {@endtemplate} -class UITextField extends StatelessWidget { - /// {@macro text_field} - const UITextField({ - super.key, - this.initialValue, - this.autoFillHints, - this.controller, - this.inputFormatters, - this.autocorrect = true, - this.readOnly = false, - this.hintText, - this.errorText, - this.prefix, - this.suffix, - this.keyboardType, - this.onChanged, - this.onSubmitted, - this.onTap, - this.obscureText, - }); - - /// A text field component based on material [TextFormField] widget which - /// extends TextField with email icon and validates input type. - const UITextField.emailTextField({ - super.key, - this.initialValue, - this.controller, - this.inputFormatters, - this.readOnly = false, - this.hintText, - this.errorText, - this.suffix, - this.onChanged, - this.onSubmitted, - this.onTap, - this.obscureText, - }) : keyboardType = TextInputType.emailAddress, - autoFillHints = const [AutofillHints.email], - prefix = const Padding( - padding: EdgeInsets.only( - left: UISpacing.sm, - right: UISpacing.sm, - ), - child: Icon( - Icons.email, - color: UIColors.grey, - ), - ), - autocorrect = false; - - /// A password text field component based on material [TextFormField] - /// widget which extends TextField which obscures the text - const UITextField.passwordTextField({ - super.key, - this.initialValue, - this.controller, - this.inputFormatters, - this.readOnly = false, - this.errorText, - this.suffix, - this.onChanged, - this.onSubmitted, - this.onTap, - this.keyboardType, - this.autoFillHints, - this.prefix, - this.obscureText = true, - }) : hintText = 'Password', - autocorrect = false; - - /// A value to initialize the field to. - final String? initialValue; - - /// List of auto fill hints. - final Iterable? autoFillHints; - - /// Controls the text being edited. - /// - /// If null, this widget will create its own [TextEditingController] and - /// initialize its [TextEditingController.text] with [initialValue]. - final TextEditingController? controller; - - /// Optional input validation and formatting overrides. - final List? inputFormatters; - - /// Whether to enable autocorrect. - /// Defaults to true. - final bool autocorrect; - - /// Whether the text field should be read-only. - /// Defaults to false. - final bool readOnly; - - /// Text that suggests what sort of input the field accepts. - final String? hintText; - - /// Text that appears below the field. - final String? errorText; - - /// A widget that appears before the editable part of the text field. - final Widget? prefix; - - /// A widget that appears after the editable part of the text field. - final Widget? suffix; - - /// The type of keyboard to use for editing the text. - /// Defaults to [TextInputType.text] if maxLines is one and - /// [TextInputType.multiline] otherwise. - final TextInputType? keyboardType; - - /// Called when the user inserts or deletes texts in the text field. - final ValueChanged? onChanged; - - /// {@macro flutter.widgets.editableText.onSubmitted} - final ValueChanged? onSubmitted; - - /// Called when the text field has been tapped. - final VoidCallback? onTap; - - /// Whether the text field should obscure the text being edited. - final bool? obscureText; - - @override - Widget build(BuildContext context) { - return ConstrainedBox( - constraints: const BoxConstraints(minHeight: 80), - child: TextFormField( - key: key, - initialValue: initialValue, - controller: controller, - inputFormatters: inputFormatters, - keyboardType: keyboardType, - obscureText: obscureText ?? false, - autocorrect: autocorrect, - readOnly: readOnly, - autofillHints: autoFillHints, - cursorColor: Theme.of(context).colorScheme.onSurface, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w500, - ), - onFieldSubmitted: onSubmitted, - decoration: InputDecoration( - hintText: hintText, - hintStyle: Theme.of(context).textTheme.labelLarge, - errorText: errorText, - prefixIcon: prefix, - suffixIcon: suffix, - suffixIconConstraints: const BoxConstraints.tightFor( - width: 32, - height: 32, - ), - prefixIconConstraints: const BoxConstraints.tightFor( - width: 48, - ), - ), - onChanged: onChanged, - onTap: onTap, - ), - ); - } -} diff --git a/todo/packages/todo_ui/lib/src/widgets/widgets.dart b/todo/packages/todo_ui/lib/src/widgets/widgets.dart deleted file mode 100644 index c8dd9e5..0000000 --- a/todo/packages/todo_ui/lib/src/widgets/widgets.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'todo_app_bar.dart'; -export 'todo_outlined_button.dart'; -export 'todo_text_field.dart'; diff --git a/todo/packages/todo_ui/lib/todo_ui.dart b/todo/packages/todo_ui/lib/todo_ui.dart deleted file mode 100644 index c54186f..0000000 --- a/todo/packages/todo_ui/lib/todo_ui.dart +++ /dev/null @@ -1,7 +0,0 @@ -export 'src/colors/todo_colors.dart'; -export 'src/extensions/extensions.dart'; -export 'src/icons/icons.dart'; -export 'src/spacing/todo_spacing.dart'; -export 'src/theme/todo_theme.dart'; -export 'src/typography/typography.dart'; -export 'src/widgets/widgets.dart'; diff --git a/todo/packages/todo_ui/pubspec.yaml b/todo/packages/todo_ui/pubspec.yaml deleted file mode 100644 index bfadcd4..0000000 --- a/todo/packages/todo_ui/pubspec.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: todo_ui -description: Todo UI library -version: 0.0.1 -publish_to: none - -environment: - sdk: ^3.5.0 - flutter: 3.24.3 - -dependencies: - flutter: - sdk: flutter - flutter_asset_generator: ^1.2.0 - flutter_svg: ^2.0.10+1 - -dev_dependencies: - build_runner: ^2.4.13 - flutter_gen_runner: ^5.7.0 - flutter_test: - sdk: flutter - mockingjay: ^0.6.0 - very_good_analysis: ^6.0.0 - -flutter: - uses-material-design: true - assets: - - assets/icons/ - - assets/images/ - fonts: - - family: NotoSansDisplay - fonts: - - asset: assets/fonts/NotoSansDisplay-Regular.ttf - weight: 400 - - asset: assets/fonts/NotoSansDisplay-SemiBold.ttf - weight: 600 - -flutter_gen: - output: lib/gen/ - line_length: 80 - integrations: - flutter_svg: true diff --git a/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart b/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart deleted file mode 100644 index ff39710..0000000 --- a/todo/packages/todo_ui/test/src/extensions/theme_data_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - group('ThemeX Extension Tests', () { - test('Test light mode icons', () { - final lightTheme = ThemeData(brightness: Brightness.light); - expect(lightTheme.icons, isA()); - }); - - test('Test dark mode icons', () { - final darkTheme = ThemeData(brightness: Brightness.dark); - expect(darkTheme.icons, isA()); - }); - }); -} diff --git a/todo/packages/todo_ui/test/src/helpers/helpers.dart b/todo/packages/todo_ui/test/src/helpers/helpers.dart deleted file mode 100644 index b15fe65..0000000 --- a/todo/packages/todo_ui/test/src/helpers/helpers.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pump_app.dart'; diff --git a/todo/packages/todo_ui/test/src/helpers/pump_app.dart b/todo/packages/todo_ui/test/src/helpers/pump_app.dart deleted file mode 100644 index 7b95b1d..0000000 --- a/todo/packages/todo_ui/test/src/helpers/pump_app.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -extension UITester on WidgetTester { - Future pumpApp( - Widget widgetUnderTest, { - ThemeData? theme, - }) async { - await pumpWidget( - MaterialApp( - theme: theme, - home: Scaffold( - body: widgetUnderTest, - ), - ), - ); - await pump(); - } -} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart deleted file mode 100644 index 3e578ba..0000000 --- a/todo/packages/todo_ui/test/src/icons/todo_icons_dark_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - group('UIIconsDark Tests', () { - test('Test Back icon', () { - final appIcons = UIIconsDark(); - final svgPicture = appIcons.backIcon(); - - expect(svgPicture, isA()); - }); - - test('Test email logo icon', () { - final appIcons = UIIconsDark(); - final svgPicture = appIcons.emailOutline(); - - expect(svgPicture, isA()); - }); - }); -} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart deleted file mode 100644 index c9c532f..0000000 --- a/todo/packages/todo_ui/test/src/icons/todo_icons_light_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - group('UIIconsLight Tests', () { - test('Test Back icon', () { - final appIcons = UIIconsLight(); - final svgPicture = appIcons.backIcon(); - - expect(svgPicture, isA()); - }); - - test('Test email logo icon', () { - final appIcons = UIIconsLight(); - final svgPicture = appIcons.emailOutline(); - - expect(svgPicture, isA()); - }); - }); -} diff --git a/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart b/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart deleted file mode 100644 index 9b82685..0000000 --- a/todo/packages/todo_ui/test/src/icons/todo_icons_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -class TestUIIcon extends UIIcon {} - -void main() { - group('UIIcons Tests', () { - test('Test Back icon', () { - final appIcons = TestUIIcon(); - final svgPicture = appIcons.backIcon(); - - expect(svgPicture, isA()); - }); - - test('Test email logo icon', () { - final appIcons = TestUIIcon(); - final svgPicture = appIcons.emailOutline(); - - expect(svgPicture, isA()); - }); - }); -} diff --git a/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart b/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart deleted file mode 100644 index 24c51d7..0000000 --- a/todo/packages/todo_ui/test/src/typography/todo_text_styles_test.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - group('TextStyles', () { - group('UITextStyle', () { - test('display2 returns TextStyle', () { - expect(UITextStyle.display2, isA()); - }); - - test('display3 returns TextStyle', () { - expect(UITextStyle.display3, isA()); - }); - - test('headline1 returns TextStyle', () { - expect(UITextStyle.headline1, isA()); - }); - - test('headline2 returns TextStyle', () { - expect(UITextStyle.headline2, isA()); - }); - - test('headline3 returns TextStyle', () { - expect(UITextStyle.headline3, isA()); - }); - - test('subtitle1 returns TextStyle', () { - expect(UITextStyle.subtitle1, isA()); - }); - - test('subtitle2 returns TextStyle', () { - expect(UITextStyle.subtitle2, isA()); - }); - - test('bodyText1 returns TextStyle', () { - expect(UITextStyle.bodyText1, isA()); - }); - - test('bodyText2 returns TextStyle', () { - expect(UITextStyle.bodyText2, isA()); - }); - - test('button returns TextStyle', () { - expect(UITextStyle.button, isA()); - }); - - test('caption returns TextStyle', () { - expect(UITextStyle.caption, isA()); - }); - - test('overline returns TextStyle', () { - expect(UITextStyle.overline, isA()); - }); - - test('labelSmall returns TextStyle', () { - expect(UITextStyle.labelSmall, isA()); - }); - }); - }); -} diff --git a/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart deleted file mode 100644 index a33f031..0000000 --- a/todo/packages/todo_ui/test/src/widgets/todo_app_bar_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; -void main() { - testWidgets('UIAppBar displays with actions', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: UIAppBar.actionAppBar( - textTitle: 'Action AppBar', - ), - ), - ), - ); - - expect(find.text('Action AppBar'), findsOneWidget); - - expect(find.text('Action 1'), findsOneWidget); - - final appBar = tester.widget(find.byType(AppBar)); - expect(appBar.elevation, 3); - }); - - testWidgets('UIAppBar displays with red background color', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: UIAppBar.redAppBar( - textTitle: 'Red AppBar', - ), - ), - ), - ); - - expect(find.text('Red AppBar'), findsOneWidget); - - final appBar = tester.widget(find.byType(AppBar)); - expect(appBar.backgroundColor, UIColors.red); - - expect(appBar.elevation, 5); - }); -} diff --git a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart deleted file mode 100644 index 877a60e..0000000 --- a/todo/packages/todo_ui/test/src/widgets/todo_outlined_button_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - testWidgets('UIOutlinedButton displays default properties', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: UIOutlinedButton( - text: 'Default Button', - onPressed: () {}, - ), - ), - ), - ); - - expect(find.text('Default Button'), findsOneWidget); - - final outlinedButton = - tester.widget(find.byType(OutlinedButton)); - expect(outlinedButton.onPressed, isNotNull); - }); - - testWidgets('UIOutlinedButton primary displays correct properties', - (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: UIOutlinedButton.primary( - text: 'Primary Button', - onPressed: () {}, - ), - ), - ), - ); - - expect(find.text('Primary Button'), findsOneWidget); - - final outlinedButton = - tester.widget(find.byType(OutlinedButton)); - expect(outlinedButton.onPressed, isNotNull); - - final container = - tester.widget(find.byType(OutlinedButton)); - expect(container.style?.backgroundColor?.resolve({}), UIColors.lightBlue200); - - final textWidget = tester.widget(find.text('Primary Button')); - expect(textWidget.style?.color, UIColors.black); - }); - - testWidgets('UIOutlinedButton secondary displays correct properties', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: UIOutlinedButton.secondary( - text: 'Secondary Button', - onPressed: () {}, - ), - ), - ), - ); - - expect(find.text('Secondary Button'), findsOneWidget); - - final outlinedButton = - tester.widget(find.byType(OutlinedButton)); - expect(outlinedButton.onPressed, isNotNull); - - final container = - tester.widget(find.byType(OutlinedButton)); - expect(container.style?.backgroundColor?.resolve({}), UIColors.grey); - - final textWidget = tester.widget(find.text('Secondary Button')); - expect(textWidget.style?.color, UIColors.white); - }); - - testWidgets('UIOutlinedButton displays with icon', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: UIOutlinedButton( - text: 'Button with Icon', - icon: const Icon(Icons.add), - onPressed: () {}, - ), - ), - ), - ); - - expect(find.text('Button with Icon'), findsOneWidget); - - expect(find.byIcon(Icons.add), findsOneWidget); - }); - - testWidgets('UIOutlinedButton displays as disabled when onPressed is null', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: UIOutlinedButton( - text: 'Disabled Button', - ), - ), - ), - ); - - expect(find.text('Disabled Button'), findsOneWidget); - - final outlinedButton = - tester.widget(find.byType(OutlinedButton)); - expect(outlinedButton.onPressed, isNull); - - final theme = Theme.of(tester.element(find.byType(UIOutlinedButton))); - final textWidget = tester.widget(find.text('Disabled Button')); - expect(textWidget.style?.color, theme.colorScheme.onSurface); - }); -} \ No newline at end of file diff --git a/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart b/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart deleted file mode 100644 index 2e3c653..0000000 --- a/todo/packages/todo_ui/test/src/widgets/todo_text_field_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - testWidgets('UITextField displays default properties', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: UITextField( - hintText: 'Default TextField', - ), - ), - ), - ); - - expect(find.text('Default TextField'), findsOneWidget); - - final textField = tester.widget(find.byType(TextFormField)); - expect(textField.enabled, isTrue); - }); - - testWidgets('UITextField displays with initial value', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: UITextField( - initialValue: 'Initial Value', - ), - ), - ), - ); - - final textField = tester.widget(find.byType(TextFormField)); - expect(textField.initialValue, 'Initial Value'); - }); - - testWidgets('UITextField displays error text', ( - WidgetTester tester, - ) async { - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: UITextField( - hintText: 'TextField with Error', - errorText: 'Error Text', - ), - ), - ), - ); - - expect(find.text('Error Text'), findsOneWidget); - }); -} diff --git a/todo/packages/todo_ui/test/theme/todo_theme_test.dart b/todo/packages/todo_ui/test/theme/todo_theme_test.dart deleted file mode 100644 index 0a03d8d..0000000 --- a/todo/packages/todo_ui/test/theme/todo_theme_test.dart +++ /dev/null @@ -1,324 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo_ui/todo_ui.dart'; - -void main() { - group('UILightTheme', () { - group('themeData', () { - group('color', () { - test('primary is sky blue', () { - expect( - UITheme().lightTheme.colorScheme.primary, - UIColors.skyBlue, - ); - }); - - test('secondary is UIColors.paleSky', () { - expect( - UITheme().lightTheme.colorScheme.secondary, - UIColors.paleSky, - ); - }); - }); - - group('divider', () { - test('horizontal padding is UISpacing.lg', () { - expect( - UITheme().lightTheme.dividerTheme.indent, - UISpacing.lg, - ); - expect( - UITheme().lightTheme.dividerTheme.endIndent, - UISpacing.lg, - ); - }); - - test('space is UISpacing.lg', () { - expect( - UITheme().lightTheme.dividerTheme.space, - UISpacing.lg, - ); - }); - }); - - group('switchTheme', () { - group('thumbColor', () { - test('returns darkAqua when selected', () { - expect( - UITheme() - .lightTheme - .switchTheme - .thumbColor - ?.resolve({WidgetState.selected}), - equals(UIColors.darkAqua), - ); - }); - - test('returns black when not selected', () { - expect( - UITheme().lightTheme.switchTheme.thumbColor?.resolve({}), - equals(UIColors.black), - ); - }); - }); - - group('trackColor', () { - test('returns primaryContainer when selected', () { - expect( - UITheme() - .lightTheme - .switchTheme - .trackColor - ?.resolve({WidgetState.selected}), - equals(UIColors.primaryContainer), - ); - }); - - test('returns paleSky when not selected', () { - expect( - UITheme().lightTheme.switchTheme.trackColor?.resolve({}), - equals(UIColors.paleSky), - ); - }); - }); - }); - - group('progressIndicatorTheme', () { - test('color is UIColors.darkAqua', () { - expect( - UITheme().lightTheme.progressIndicatorTheme.color, - equals(UIColors.darkAqua), - ); - }); - - test('circularTrackColor is UIColors.borderOutline', () { - expect( - UITheme() - .lightTheme - .progressIndicatorTheme - .circularTrackColor, - equals(UIColors.borderOutline), - ); - }); - }); - - group('lightOutlinedButtonTheme', () { - test('shape is StadiumBorder', () { - expect( - UITheme().lightTheme.outlinedButtonTheme.style?.shape?.resolve({}), - const StadiumBorder(), - ); - }); - - test('background color is UIColors.white', () { - expect( - UITheme() - .lightTheme - .outlinedButtonTheme - .style - ?.backgroundColor! - .resolve({}), - equals(UIColors.white), - ); - }); - - test('side is BorderSide', () { - expect( - UITheme().lightTheme.outlinedButtonTheme.style?.side!.resolve({}), - equals(const BorderSide()), - ); - }); - - test( - 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', - () { - expect( - UITheme().lightTheme.outlinedButtonTheme.style?.padding!.resolve({}), - equals( - const EdgeInsets.symmetric( - horizontal: UISpacing.xlg, - vertical: UISpacing.lg, - ), - ), - ); - }); - - test('alignment is Alignment.center', () { - expect( - UITheme().lightTheme.outlinedButtonTheme.style?.alignment, - equals(Alignment.center), - ); - }); - - test('text style for disabled state', () { - final disabledTextStyle = UITheme() - .lightTheme - .outlinedButtonTheme - .style - ?.textStyle! - .resolve({WidgetState.disabled}); - expect( - disabledTextStyle?.color, - equals(UIColors.black), - ); - expect( - disabledTextStyle?.fontWeight, - equals(FontWeight.w500), - ); - }); - - test('text style for enabled state', () { - final enabledTextStyle = UITheme() - .lightTheme - .outlinedButtonTheme - .style - ?.textStyle - ?.resolve({}); - expect( - enabledTextStyle?.color, - equals(UIColors.white), - ); - expect( - enabledTextStyle?.fontWeight, - equals(FontWeight.w500), - ); - }); - }); - }); - }); - - group('UIDarkTheme', () { - group('themeData', () { - group('color', () { - test('primary is blue', () { - expect( - UITheme().darkTheme.colorScheme.primary, - UIColors.blue, - ); - }); - - test('secondary is UIColors.paleSky', () { - expect( - UITheme().darkTheme.colorScheme.secondary, - UIColors.paleSky, - ); - }); - - test('background is black', () { - expect( - UITheme().darkTheme.colorScheme.surface, - UIColors.black, - ); - }); - }); - - group('divider', () { - test('horizontal padding is UISpacing.lg', () { - expect( - UITheme().darkTheme.dividerTheme.indent, - UISpacing.lg, - ); - expect( - UITheme().darkTheme.dividerTheme.endIndent, - UISpacing.lg, - ); - }); - - test('space is UISpacing.lg', () { - expect( - UITheme().darkTheme.dividerTheme.space, - UISpacing.lg, - ); - }); - }); - - group('darkOutlinedButtonTheme', () { - test('shape is StadiumBorder', () { - expect( - UITheme().darkTheme.outlinedButtonTheme.style?.shape?.resolve({}), - const StadiumBorder(), - ); - }); - - test('background color is UIColors.black', () { - expect( - UITheme() - .darkTheme - .outlinedButtonTheme - .style - ?.backgroundColor - ?.resolve({}), - equals(UIColors.black), - ); - }); - - test('side color is UIColors.white', () { - expect( - UITheme() - .darkTheme - .outlinedButtonTheme - .style - ?.side - ?.resolve({})?.color, - equals(UIColors.white), - ); - }); - - test( - 'padding is EdgeInsets.symmetric(horizontal: xlg, vertical: lg)', - () { - expect( - UITheme().darkTheme.outlinedButtonTheme.style?.padding?.resolve({}), - equals( - const EdgeInsets.symmetric( - horizontal: UISpacing.xlg, - vertical: UISpacing.lg, - ), - ), - ); - }); - - test('alignment is Alignment.center', () { - expect( - UITheme().darkTheme.outlinedButtonTheme.style?.alignment, - equals(Alignment.center), - ); - }); - - test('text style for disabled state', () { - final disabledTextStyle = UITheme() - .darkTheme - .outlinedButtonTheme - .style - ?.textStyle - ?.resolve({WidgetState.disabled}); - expect( - disabledTextStyle?.color, - equals(UIColors.white), - ); - expect( - disabledTextStyle?.fontWeight, - equals(FontWeight.w500), - ); - }); - - test('text style for enabled state', () { - final enabledTextStyle = UITheme() - .darkTheme - .outlinedButtonTheme - .style - ?.textStyle - ?.resolve({}); - expect( - enabledTextStyle?.color, - equals(UIColors.white), - ); - expect( - enabledTextStyle?.fontWeight, - equals(FontWeight.w500), - ); - }); - }); - }); - }); -} diff --git a/todo/packages/token_provider/.gitignore b/todo/packages/token_provider/.gitignore deleted file mode 100644 index 526da15..0000000 --- a/todo/packages/token_provider/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# See https://www.dartlang.org/guides/libraries/private-files - -# Files and directories created by pub -.dart_tool/ -.packages -build/ -pubspec.lock \ No newline at end of file diff --git a/todo/packages/token_provider/analysis_options.yaml b/todo/packages/token_provider/analysis_options.yaml deleted file mode 100644 index 4f94586..0000000 --- a/todo/packages/token_provider/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.5.1.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/token_provider/lib/src/token_provider.dart b/todo/packages/token_provider/lib/src/token_provider.dart deleted file mode 100644 index 02361d1..0000000 --- a/todo/packages/token_provider/lib/src/token_provider.dart +++ /dev/null @@ -1,14 +0,0 @@ -/// {@template token_provider} -/// Provides an abstraction for access token retrieval. -/// {@endtemplate} -abstract class TokenProvider { - /// {@macro token_provider} - const TokenProvider(); - - /// Fetches the current access token available in the session - /// returns null when no session is active at the moment - Future fetchToken(); - - /// Send a request to the provider to refresh the token - Future requestTokenRefresh(); -} diff --git a/todo/packages/token_provider/lib/token_provider.dart b/todo/packages/token_provider/lib/token_provider.dart deleted file mode 100644 index 0cfd149..0000000 --- a/todo/packages/token_provider/lib/token_provider.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// Package responsible to provide an abstraction for access token retrieval -library token_provider; - -export 'src/token_provider.dart'; diff --git a/todo/packages/token_provider/pubspec.yaml b/todo/packages/token_provider/pubspec.yaml deleted file mode 100644 index b08b462..0000000 --- a/todo/packages/token_provider/pubspec.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: token_provider -description: Package responsible to provide an abstraction for access token retrieval -version: 0.1.0+1 -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dev_dependencies: - mocktail: ^1.0.4 - test: ^1.25.8 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/token_provider/test/src/token_provider_test.dart b/todo/packages/token_provider/test/src/token_provider_test.dart deleted file mode 100644 index 18af29e..0000000 --- a/todo/packages/token_provider/test/src/token_provider_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:test/test.dart'; -import 'package:token_provider/token_provider.dart'; - -class DummyTokenProvider extends TokenProvider { - @override - Future fetchToken() { - throw UnimplementedError(); - } - - @override - Future requestTokenRefresh() { - throw UnimplementedError(); - } -} - -void main() { - group('TokenProvider', () { - test('can be instantiated', () { - expect(DummyTokenProvider(), isNotNull); - }); - }); -} diff --git a/todo/packages/user_repository/.gitignore b/todo/packages/user_repository/.gitignore deleted file mode 100644 index 526da15..0000000 --- a/todo/packages/user_repository/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# See https://www.dartlang.org/guides/libraries/private-files - -# Files and directories created by pub -.dart_tool/ -.packages -build/ -pubspec.lock \ No newline at end of file diff --git a/todo/packages/user_repository/analysis_options.yaml b/todo/packages/user_repository/analysis_options.yaml deleted file mode 100644 index 97694e0..0000000 --- a/todo/packages/user_repository/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:very_good_analysis/analysis_options.6.0.0.yaml -analyzer: - exclude: - - "**/*.g.dart" diff --git a/todo/packages/user_repository/lib/src/exceptions/exceptions.dart b/todo/packages/user_repository/lib/src/exceptions/exceptions.dart deleted file mode 100644 index 6b5557e..0000000 --- a/todo/packages/user_repository/lib/src/exceptions/exceptions.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:user_repository/user_repository.dart'; - -/// {@template user_repository_exception} -/// Base class for all [UserRepository] exceptions. -/// {@endtemplate} -abstract class UserRepositoryException implements Exception { - /// {@macro user_repository_exception} - const UserRepositoryException(this.error, this.stackTrace); - - /// The error which was caught. - final Object error; - - /// The error stack trace. - final StackTrace stackTrace; -} - -/// {@template sign_out_failure} -/// Thrown during the sign out process if a failure occurs. -/// {@endtemplate} -class SignOutFailure extends UserRepositoryException { - /// {@macro sign_out_failure} - const SignOutFailure(super.error, super.stackTrace); -} - -/// {@template user_delete_account_failure} -/// Thrown when an error occurs on the delete account process. -/// {@endtemplate} -class UserDeleteAccountFailure extends UserRepositoryException { - /// {@macro sign_out_failure} - const UserDeleteAccountFailure(super.error, super.stackTrace); -} - -/// {@template user_reauthenticate_failure} -/// Thrown when an error occurs on the reauthentication process. -/// {@endtemplate} -class UserReAuthenticateFailure extends UserRepositoryException { - /// {@macro sign_out_failure} - const UserReAuthenticateFailure(super.error, super.stackTrace); -} diff --git a/todo/packages/user_repository/lib/src/models/models.dart b/todo/packages/user_repository/lib/src/models/models.dart deleted file mode 100644 index 00db202..0000000 --- a/todo/packages/user_repository/lib/src/models/models.dart +++ /dev/null @@ -1 +0,0 @@ -export 'user.dart'; diff --git a/todo/packages/user_repository/lib/src/models/user.dart b/todo/packages/user_repository/lib/src/models/user.dart deleted file mode 100644 index c9a3521..0000000 --- a/todo/packages/user_repository/lib/src/models/user.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'user.g.dart'; - -/// {@template user} -/// A model representing a todo user. -/// {@endtemplate} -@JsonSerializable() -class User extends Equatable { - /// {@macro user} - const User({ - required this.id, - required this.email, - }); - - /// {@macro user} - factory User.fromJson(Map json) { - return _$UserFromJson(json); - } - - /// The user unique identifer. - final String id; - - /// The user email address. - final String email; - - @override - List get props => [ - id, - email, - ]; - - /// Returns a json built from this user. - Map toJson() { - return _$UserToJson(this); - } -} diff --git a/todo/packages/user_repository/lib/src/models/user.g.dart b/todo/packages/user_repository/lib/src/models/user.g.dart deleted file mode 100644 index 0b08534..0000000 --- a/todo/packages/user_repository/lib/src/models/user.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -User _$UserFromJson(Map json) => User( - id: json['id'] as String, - email: json['email'] as String, - ); - -Map _$UserToJson(User instance) => { - 'id': instance.id, - 'email': instance.email, - }; diff --git a/todo/packages/user_repository/lib/src/user_repository.dart b/todo/packages/user_repository/lib/src/user_repository.dart deleted file mode 100644 index 4a31333..0000000 --- a/todo/packages/user_repository/lib/src/user_repository.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:async'; - -import 'package:api_client/api_client.dart'; -import 'package:auth_client/auth_client.dart'; -import 'package:user_repository/user_repository.dart'; - -/// {@template user_repository} -/// A repository to handle all User related logic. -/// {@endtemplate} -class UserRepository { - /// {@macro user_repository} - UserRepository({ - required AuthenticationClient authenticationClient, - required ApiClient apiClient, - }) : _apiClient = apiClient, - _authenticationClient = authenticationClient; - - final ApiClient _apiClient; - final AuthenticationClient _authenticationClient; - - /// Maps the [AuthenticationEvent] to a [User] object. - Future onAuthStatusChanged(AuthenticationEvent event) async { - if (!event.type.isSignedIn) { - return null; - } else { - final authUser = event.user; - - final user = User( - id: authUser.id, - email: authUser.email, - ); - - return user; - } - } - - /// Emits a new value when the authentication status changes. - /// - /// If the authentication status transitions from unauthenticated to - /// authenticated, the [Stream] emits a valid [User] object. - /// - /// If the transition is from authenticated to unauthenticated, the [Stream] - /// emits null. - Stream get user { - return _authenticationClient.onAuthStatusChanged.asyncMap( - onAuthStatusChanged, - ); - } - - /// Attempts to sign up the user with [email], [password] and [name]. - /// - /// Throws a [SignUpFailure] if an exception occurs. - Future signUp({ - required String email, - required String password, - required String name, - }) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - assert( - password.isNotEmpty, - () => throw ArgumentError('Password must not be empty'), - ); - assert( - name.isNotEmpty, - () => throw ArgumentError('Name must not be empty'), - ); - try { - return _apiClient.userResource.signUp( - SignUpRequest( - email: email, - password: password, - name: name, - ), - ); - } on Exception { - rethrow; - } - } - - /// Attempts to verify email with [email] and [code]. - /// - /// Throws a [EmailVerificationFailure] if an exception occurs. - Future verifyEmail({ - required String email, - required String code, - }) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - assert( - code.isNotEmpty, - () => throw ArgumentError('Code must not be empty'), - ); - try { - return _apiClient.userResource.verifyEmail( - EmailVerificationRequest( - email: email, - code: code, - ), - ); - } on EmailVerificationFailure catch (error, stackTrace) { - throw EmailVerificationFailure(error, stackTrace); - } on Exception catch (error, stackTrace) { - throw EmailVerificationFailure(error, stackTrace); - } - } - - /// Attempts to recover password for user with [email]. - /// - /// Throws a [ForgotPasswordFailure] if an exception occurs. - Future forgotPassword({required String email}) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - try { - return _apiClient.userResource.forgotPassword(email); - } on Exception catch (error, stackTrace) { - throw ForgotPasswordFailure(error, stackTrace); - } - } - - /// Attempts to update password with given [email], [password] and [code]. - /// - /// Throws a [UpdatePasswordFailure] if an exception occurs. - Future updatePassword({ - required String email, - required String password, - required String code, - }) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - assert( - password.isNotEmpty, - () => throw ArgumentError('Password must not be empty'), - ); - assert( - code.isNotEmpty, - () => throw ArgumentError('Code must not be empty'), - ); - try { - return _apiClient.userResource.updatePassword( - UpdatePasswordRequest( - email: email, - password: password, - confirmationCode: code, - ), - ); - } on Exception catch (error, stackTrace) { - throw UpdatePasswordFailure(error, stackTrace); - } - } - - /// Attempts to resend verification code for user with [email]. - /// - /// Throws a [SendOtpCodeFailure] if an exception occurs. - Future sendOtpCode({required String email}) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - try { - return _apiClient.userResource.sendOtpCode(email); - } on SendOtpCodeFailure catch (error, stackTrace) { - throw SendOtpCodeFailure(error, stackTrace); - } on Exception catch (error, stackTrace) { - throw SendOtpCodeFailure(error, stackTrace); - } - } - - /// Attempts to sign the user identified by [email] into the MessageDesk - /// app by using the [password]. - /// - /// Throws a [SignInFailure] if an exception occurs. - Future signIn({ - required String email, - required String password, - }) async { - assert( - email.isNotEmpty, - () => throw ArgumentError('Email must not be empty'), - ); - assert( - password.isNotEmpty, - () => throw ArgumentError('Password must not be empty'), - ); - try { - return _authenticationClient.signInUser( - email: email, - password: password, - ); - } on InvalidUserFailure catch (error, stackTrace) { - throw InvalidUserFailure(error, stackTrace); - } on Exception catch (error, stackTrace) { - throw SignInFailure(error, stackTrace); - } - } - - /// Signs out the current user. - /// - /// Throws a [SignOutFailure] if an exception occurs. - Future signOut() async { - try { - await _authenticationClient.signOut(); - } catch (error, stackTrace) { - throw SignOutFailure(error, stackTrace); - } - } - - /// Deletes the current user - /// - /// Throws a [DeleteAccountFailure] if an exception occurs. - Future deleteAccount() async { - try { - await _authenticationClient.deleteAccount(); - } catch (error, stackTrace) { - throw UserDeleteAccountFailure(error, stackTrace); - } - } - - /// Re-authenticates the current user - /// - /// Throws a [UserReAuthenticateFailure] if an exception occurs. - Future reAuthenticate({ - required String email, - required String password, - }) async { - try { - await _authenticationClient.reAuthenticate( - email: email, - password: password, - ); - } catch (error, stackTrace) { - throw UserReAuthenticateFailure(error, stackTrace); - } - } -} diff --git a/todo/packages/user_repository/lib/user_repository.dart b/todo/packages/user_repository/lib/user_repository.dart deleted file mode 100644 index 70e7fa3..0000000 --- a/todo/packages/user_repository/lib/user_repository.dart +++ /dev/null @@ -1,6 +0,0 @@ -/// A repository to handle all User related logic. -library user_repository; - -export 'src/exceptions/exceptions.dart'; -export 'src/models/models.dart'; -export 'src/user_repository.dart'; diff --git a/todo/packages/user_repository/pubspec.yaml b/todo/packages/user_repository/pubspec.yaml deleted file mode 100644 index e533333..0000000 --- a/todo/packages/user_repository/pubspec.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: user_repository -description: A repository to handle all User related logic. -version: 0.1.0+1 -publish_to: none - -environment: - sdk: ">=3.3.0 <4.0.0" - -dependencies: - api_client: - path: ../api_client - auth_client: - path: ../auth_client - equatable: ^2.0.5 - json_annotation: ^4.9.0 - -dev_dependencies: - build_runner: ^2.4.13 - json_serializable: ^6.8.0 - mocktail: ^1.0.4 - test: ^1.25.8 - very_good_analysis: ^6.0.0 diff --git a/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart b/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart deleted file mode 100644 index d40cd39..0000000 --- a/todo/packages/user_repository/test/src/exceptions/exceptions_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:test/test.dart'; -import 'package:user_repository/user_repository.dart'; - -void main() { - group('SignOutFailure', () { - test('constructor initializes properties', () { - final error = Exception('Test error'); - final stackTrace = StackTrace.current; - - final failure = SignOutFailure(error, stackTrace); - - expect(failure.error, equals(error)); - expect(failure.stackTrace, equals(stackTrace)); - }); - - test('user delete exception', () { - final error = Exception('Test error'); - final stackTrace = StackTrace.current; - - final failure = UserDeleteAccountFailure(error, stackTrace); - - expect(failure.error, equals(error)); - expect(failure.stackTrace, equals(stackTrace)); - }); - - test('user reauthenticate exception', () { - final error = Exception('Test error'); - final stackTrace = StackTrace.current; - - final failure = UserReAuthenticateFailure(error, stackTrace); - - expect(failure.error, equals(error)); - expect(failure.stackTrace, equals(stackTrace)); - }); - }); -} diff --git a/todo/packages/user_repository/test/src/models/user_test.dart b/todo/packages/user_repository/test/src/models/user_test.dart deleted file mode 100644 index 67d12e1..0000000 --- a/todo/packages/user_repository/test/src/models/user_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:test/test.dart'; -import 'package:user_repository/user_repository.dart'; - -void main() { - group('User', () { - const validId = '123'; - const validEmail = 'test@example.com'; - - test('constructor initializes properties', () { - const user = User(id: validId, email: validEmail); - - expect(user.id, equals(validId)); - expect(user.email, equals(validEmail)); - }); - - test('props list contains id and email', () { - const user = User(id: validId, email: validEmail); - - expect(user.props, containsAll([validId, validEmail])); - }); - - test('supports value equality', () { - const userA = User(id: 'A', email: 'A'); - const secondUserA = User(id: 'A', email: 'A'); - const userB = User(id: 'B', email: 'B'); - - expect(userA, equals(secondUserA)); - expect(userA, isNot(equals(userB))); - }); - - test('fromJson and toJson', () { - final json = { - 'id': validId, - 'email': validEmail, - }; - - final user = User.fromJson(json); - - expect(user.id, equals(json['id'])); - expect(user.email, equals(json['email'])); - - final toJson = user.toJson(); - - expect(toJson['id'], equals(user.id)); - expect(toJson['email'], equals(user.email)); - }); - }); -} diff --git a/todo/packages/user_repository/test/src/user_repository_test.dart b/todo/packages/user_repository/test/src/user_repository_test.dart deleted file mode 100644 index aa080d7..0000000 --- a/todo/packages/user_repository/test/src/user_repository_test.dart +++ /dev/null @@ -1,546 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'package:api_client/api_client.dart'; -import 'package:auth_client/auth_client.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; -import 'package:user_repository/user_repository.dart'; - -class MockApiClient extends Mock implements ApiClient {} - -class MockAuthenticationClient extends Mock implements AuthenticationClient {} - -class MockUserResource extends Mock implements UserResource {} - -void main() { - group('UserRepository', () { - late ApiClient apiClient; - late AuthenticationClient authenticationClient; - late UserResource userResource; - late UserRepository repository; - - const validEmail = 'example@mail.com'; - const validPassword = 'password'; - const validCode = '12345'; - const validName = 'Name'; - final validUser = AuthenticationUser(id: 'id', email: 'email'); - - setUp(() { - apiClient = MockApiClient(); - authenticationClient = MockAuthenticationClient(); - userResource = MockUserResource(); - repository = UserRepository( - apiClient: apiClient, - authenticationClient: authenticationClient, - ); - }); - - test('can be instantiated', () { - expect( - UserRepository( - apiClient: apiClient, - authenticationClient: authenticationClient, - ), - isNotNull, - ); - }); - - test('can be instantiated', () { - expect( - UserRepository( - apiClient: apiClient, - authenticationClient: authenticationClient, - ), - isNotNull, - ); - }); - - group('Authentication status changed', () { - test('authentication event with session expired state', () async { - final authenticationEvent = - AuthenticationEvent(type: AuthEventType.sessionExpired); - final user = await repository.onAuthStatusChanged(authenticationEvent); - - expect(user, null); - }); - - test('authentication event with signed out state', () async { - final authenticationEvent = - AuthenticationEvent(type: AuthEventType.signedOut); - final user = await repository.onAuthStatusChanged(authenticationEvent); - - expect(user, null); - }); - - test('authentication event with user deleted state', () async { - final authenticationEvent = - AuthenticationEvent(type: AuthEventType.userDeleted); - final user = await repository.onAuthStatusChanged(authenticationEvent); - - expect(user, null); - }); - - test('authentication event with signed in state', () async { - final authenticationEvent = - AuthenticationEvent(type: AuthEventType.signedIn, user: validUser); - final user = await repository.onAuthStatusChanged(authenticationEvent); - - expect(user, isA()); - }); - - test('calls auth status change on AuthenticationClient', () { - when(() => authenticationClient.onAuthStatusChanged).thenAnswer( - (_) => const Stream.empty(), - ); - repository.user; - verify(() => authenticationClient.onAuthStatusChanged).called(1); - }); - }); - - group('Sign up', () { - test('signs up user success', () { - when(() => apiClient.userResource).thenReturn(userResource); - when( - () => userResource.signUp( - SignUpRequest( - email: validEmail, - password: validPassword, - name: validName, - ), - ), - ).thenAnswer((_) async {}); - - expect( - () async => repository.signUp( - email: validEmail, - password: validPassword, - name: validName, - ), - returnsNormally, - ); - }); - - test('signs up user failure', () { - when(() => apiClient.userResource.signUp(any())).thenThrow(Exception()); - - expect( - () async => repository.signUp( - email: validEmail, - password: validPassword, - name: validName, - ), - throwsA(isA()), - ); - }); - - test('signs up user with empty mail', () { - expect( - () async => repository.signUp( - email: '', - password: validPassword, - name: validName, - ), - throwsA(isA()), - ); - }); - - test('signs up user with empty password', () { - expect( - () async => repository.signUp( - email: validEmail, - password: '', - name: validName, - ), - throwsA(isA()), - ); - }); - - test('signs up user with empty name', () async { - expect( - () async => repository.signUp( - email: validEmail, - password: validPassword, - name: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Verify email', () { - test('verify mail success', () async { - when(() => apiClient.userResource).thenReturn(userResource); - when( - () => userResource.verifyEmail( - EmailVerificationRequest( - email: validEmail, - code: validCode, - ), - ), - ).thenAnswer((_) async {}); - - expect( - () async => repository.verifyEmail( - email: validEmail, - code: validCode, - ), - returnsNormally, - ); - }); - - test('verify mail failure', () async { - when(() => apiClient.userResource.signUp(any())).thenThrow(Exception()); - - expect( - () async => repository.verifyEmail( - email: validEmail, - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('verify mail failure', () async { - when(() => apiClient.userResource.signUp(any())).thenThrow( - EmailVerificationFailure(Exception(), StackTrace.empty), - ); - - expect( - () async => repository.verifyEmail( - email: validEmail, - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('verify mail with empty mail', () async { - expect( - () async => repository.verifyEmail( - email: '', - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('verify mail with empty code', () async { - expect( - () async => repository.verifyEmail( - email: validEmail, - code: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Forgot Password', () { - test('forgot password success', () async { - when(() => apiClient.userResource).thenReturn(userResource); - when(() => userResource.forgotPassword(any())).thenAnswer((_) async {}); - - expect( - () async => repository.forgotPassword( - email: validEmail, - ), - returnsNormally, - ); - }); - - test('forgot password failure', () async { - when(() => apiClient.userResource.forgotPassword(any())) - .thenThrow(Exception()); - - expect( - () async => repository.forgotPassword( - email: validEmail, - ), - throwsA(isA()), - ); - }); - - test('forgot password with empty mail', () async { - expect( - () async => repository.forgotPassword( - email: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Update password', () { - test('update password success', () async { - when(() => apiClient.userResource).thenReturn(userResource); - when( - () => userResource.updatePassword( - UpdatePasswordRequest( - email: validEmail, - password: validPassword, - confirmationCode: validCode, - ), - ), - ).thenAnswer((_) async {}); - - expect( - () async => repository.updatePassword( - email: validEmail, - password: validPassword, - code: validCode, - ), - returnsNormally, - ); - }); - - test('update password failure', () async { - when(() => apiClient.userResource.updatePassword(any())) - .thenThrow(Exception()); - - expect( - () async => repository.updatePassword( - email: validEmail, - password: validPassword, - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('update password with empty email', () async { - expect( - () async => repository.updatePassword( - email: '', - password: validPassword, - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('update password with empty password', () async { - expect( - () async => repository.updatePassword( - email: validEmail, - password: '', - code: validCode, - ), - throwsA(isA()), - ); - }); - - test('update password with empty code', () async { - expect( - () async => repository.updatePassword( - email: validEmail, - password: validPassword, - code: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Send OTP code', () { - test('otp code sent success', () async { - when(() => apiClient.userResource).thenReturn(userResource); - when(() => userResource.sendOtpCode(any())).thenAnswer((_) async {}); - - expect( - () async => repository.sendOtpCode( - email: validEmail, - ), - returnsNormally, - ); - }); - - test('otp code sent failure', () async { - when(() => apiClient.userResource.sendOtpCode(any())) - .thenThrow(Exception()); - - expect( - () async => repository.sendOtpCode( - email: validEmail, - ), - throwsA(isA()), - ); - }); - - test('otp code sent failure', () async { - when(() => apiClient.userResource.sendOtpCode(any())).thenThrow( - SendOtpCodeFailure(Exception(), StackTrace.empty), - ); - - expect( - () async => repository.sendOtpCode( - email: validEmail, - ), - throwsA(isA()), - ); - }); - - test('otp code sent with empty email', () async { - expect( - () async => repository.sendOtpCode( - email: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Sign in', () { - test('sign in success', () async { - when( - () => authenticationClient.signInUser( - email: validEmail, - password: validPassword, - ), - ).thenAnswer((_) async {}); - - expect( - () async => repository.signIn( - email: validEmail, - password: validPassword, - ), - returnsNormally, - ); - }); - - test('sign in failure', () async { - when( - () => authenticationClient.signInUser( - email: validEmail, - password: validPassword, - ), - ).thenThrow(Exception()); - - expect( - () async => repository.signIn( - email: validEmail, - password: validPassword, - ), - throwsA(isA()), - ); - }); - - test('sign in with invalid user failure', () async { - when( - () => authenticationClient.signInUser( - email: validEmail, - password: validPassword, - ), - ).thenThrow( - InvalidUserFailure(Exception(), StackTrace.empty), - ); - - expect( - () async => repository.signIn( - email: validEmail, - password: validPassword, - ), - throwsA(isA()), - ); - }); - - test('sign in with empty mail', () async { - expect( - () async => repository.signIn( - email: '', - password: validPassword, - ), - throwsA(isA()), - ); - }); - - test('verify mail with empty password', () async { - expect( - () async => repository.signIn( - email: validEmail, - password: '', - ), - throwsA(isA()), - ); - }); - }); - - group('Sign out', () { - test('sign out user succesfully', () { - when(() => authenticationClient.signOut()).thenAnswer((_) async {}); - - expect( - () async => repository.signOut(), - returnsNormally, - ); - }); - - test('sign out user unsuccesfully', () { - when(() => authenticationClient.signOut()).thenThrow(Exception()); - - expect( - () async => repository.signOut(), - throwsA(isA()), - ); - }); - }); - - group('ReAuthenticate', () { - test('reAuthenticate succesfully', () { - when( - () => authenticationClient.reAuthenticate( - email: validEmail, - password: validEmail, - ), - ).thenAnswer((_) async {}); - - expect( - () async => repository.reAuthenticate( - email: validEmail, - password: validEmail, - ), - returnsNormally, - ); - }); - - test('reauthenticate unsuccesfully', () { - when( - () => authenticationClient.reAuthenticate( - email: validEmail, - password: validEmail, - ), - ).thenThrow(Exception()); - - expect( - () async => repository.reAuthenticate( - email: validEmail, - password: validEmail, - ), - throwsA(isA()), - ); - }); - }); - - group('Delete account', () { - test('deleted account succesfully', () { - when( - () => authenticationClient.deleteAccount(), - ).thenAnswer((_) async {}); - - expect( - () async => repository.deleteAccount(), - returnsNormally, - ); - }); - - test('deleted account unsuccesfully', () { - when( - () => authenticationClient.deleteAccount(), - ).thenThrow(Exception()); - - expect( - () async => repository.deleteAccount(), - throwsA(isA()), - ); - }); - }); - }); -} diff --git a/todo/pubspec.yaml b/todo/pubspec.yaml deleted file mode 100644 index 803d4ca..0000000 --- a/todo/pubspec.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: todo -description: A Somnio App -version: 1.0.0+1 -publish_to: none - -environment: - sdk: ^3.5.0 - flutter: 3.24.3 - -dependencies: - api_client: - path: packages/api_client - app_config_repository: - path: packages/app_config_repository - auth_client: - path: packages/auth_client - todo_ui: - path: packages/todo_ui - bloc: ^8.1.4 - equatable: ^2.0.5 - flutter: - sdk: flutter - flutter_bloc: ^8.1.6 - flutter_localizations: - sdk: flutter - form_inputs: - path: packages/form_inputs - go_router: ^14.2.9 - intl: ^0.20.2 - package_info_plus: ^8.0.2 - url_launcher: ^6.3.0 - user_repository: - path: packages/user_repository - -dev_dependencies: - bloc_test: ^9.1.7 - flutter_test: - sdk: flutter - mocktail: ^1.0.4 - very_good_analysis: ^6.0.0 - -flutter: - uses-material-design: true - generate: true diff --git a/todo/test/app/bloc/app_bloc_test.dart b/todo/test/app/bloc/app_bloc_test.dart deleted file mode 100644 index 3a8daf3..0000000 --- a/todo/test/app/bloc/app_bloc_test.dart +++ /dev/null @@ -1,262 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../helpers/pump_app.dart'; - -class MockUserRepository extends Mock implements UserRepository {} - -void main() { - late UserRepository mockUserRepository; - late AppConfigRepository appConfigRepository; - late ApiClient mockApiClient; - late AppConfigResource mockAppConfigResource; - - setUp(() { - mockUserRepository = MockUserRepository(); - mockApiClient = MockApiClient(); - mockAppConfigResource = MockAppConfigResource(); - - when(() => mockApiClient.appConfigResource) - .thenReturn(mockAppConfigResource); - when(() => mockAppConfigResource.getUpgrade()).thenAnswer( - (_) async => const Upgrade( - androidUpgradeUrl: - 'https://play.google.com/store/apps/details?id=com.example.todo', - iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', - androidBuildNumber: 200, - iosBuildNumber: 200, - ), - ); - when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( - (_) async => false, - ); - - appConfigRepository = AppConfigRepository( - buildNumber: 201, - platform: Platform.android, - apiClient: mockApiClient, - ); - }); - - const testUser = User(id: 'test', email: 'test'); - - group('AppBloc', () { - blocTest( - 'emits [AppState:AppState(null, AppStatus.unauthenticated)]' - ' when nothing is added', - setUp: () { - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - expect: () => [ - const AppState(user: null, status: AppStatus.unauthenticated), - ], - ); - - blocTest( - 'emits authenticated state after AppUserChanged event', - setUp: () { - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(testUser)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - act: (bloc) => bloc.add(const AppUserChanged(user: testUser)), - expect: () => [ - const AppState.authenticated( - user: testUser, - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ], - ); - - blocTest( - 'emits down for maintenance state after ' - 'AppDownForMaintenanceStatusChanged event', - setUp: () { - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - act: (bloc) => bloc.add( - const AppDownForMaintenanceStatusChanged( - isDownForMaintenance: true, - ), - ), - expect: () => [ - const AppState.downForMaintenance( - user: testUser, - isDownForMaintenance: true, - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - ), - const AppState(user: null, status: AppStatus.unauthenticated), - ], - ); - - blocTest( - 'emits unauthenticated state after AppLogoutRequested event', - setUp: () { - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - when(() => mockUserRepository.signOut()) - .thenAnswer((invocation) => Future.value()); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - act: (bloc) => bloc.add(const AppLogoutRequested()), - expect: () => [ - const AppState.unauthenticated( - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ], - ); - - blocTest( - 'emits previous state if !isDownForMaintenance' - ' on AppDownForMaintenanceStatusChanged event', - setUp: () { - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(testUser)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - act: (bloc) => bloc.add( - const AppDownForMaintenanceStatusChanged( - isDownForMaintenance: false, - ), - ), - expect: () => [ - const AppState.authenticated( - user: testUser, - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ], - ); - blocTest( - 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' - 'AppState:AppState(null, AppStatus.forceUpgrade)]' - ' when nothing is added', - setUp: () { - when(() => mockAppConfigResource.getUpgrade()).thenAnswer( - (_) async => const Upgrade( - androidUpgradeUrl: - 'https://play.google.com/store/apps/details?id=com.example.todo', - iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', - androidBuildNumber: 150, // Higher than 140 - iosBuildNumber: 200, - ), - ); - appConfigRepository = AppConfigRepository( - buildNumber: 140, - platform: Platform.android, - apiClient: mockApiClient, - ); - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - expect: () => [ - const AppState(user: null, status: AppStatus.unauthenticated), - const AppState(user: null, status: AppStatus.forceUpgrade), - ], - ); - blocTest( - 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' - 'AppState:AppState(null, AppStatus.forceUpgrade)]' - ' when nothing is added', - setUp: () { - when(() => mockAppConfigResource.getUpgrade()).thenAnswer( - (_) async => const Upgrade( - androidUpgradeUrl: - 'https://play.google.com/store/apps/details?id=com.example.todo', - iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', - androidBuildNumber: 200, - iosBuildNumber: 150, // Higher than 100 - ), - ); - appConfigRepository = AppConfigRepository( - buildNumber: 100, - platform: Platform.iOS, - apiClient: mockApiClient, - ); - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - expect: () => [ - const AppState(user: null, status: AppStatus.unauthenticated), - const AppState(user: null, status: AppStatus.forceUpgrade), - ], - ); - blocTest( - 'emits [AppState:AppState(null, AppStatus.unauthenticated), ' - 'AppState:AppState(null, AppStatus.downForMaintenance)]' - ' when nothing is added', - setUp: () { - when(() => mockAppConfigResource.getUpgrade()).thenAnswer( - (_) async => const Upgrade( - androidUpgradeUrl: - 'https://play.google.com/store/apps/details?id=com.example.todo', - iosUpgradeUrl: 'https://apps.apple.com/app/todo/id123456789', - androidBuildNumber: - 50, // Lower than 99 to not trigger force upgrade - iosBuildNumber: 50, // Lower than 99 to not trigger force upgrade - ), - ); - when(() => mockAppConfigResource.getDownForMaintenance()).thenAnswer( - (_) async => true, // Return true to trigger down for maintenance - ); - appConfigRepository = AppConfigRepository( - buildNumber: 99, - platform: Platform.iOS, - apiClient: mockApiClient, - ); - when(() => mockUserRepository.user) - .thenAnswer((_) => Stream.value(null)); - }, - build: () => AppBloc( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: appConfigRepository, - ), - expect: () => [ - const AppState(user: null, status: AppStatus.unauthenticated), - const AppState(user: null, status: AppStatus.downForMaintenance), - ], - ); - }); -} diff --git a/todo/test/app/bloc/app_event_test.dart b/todo/test/app/bloc/app_event_test.dart deleted file mode 100644 index dab02a3..0000000 --- a/todo/test/app/bloc/app_event_test.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -void main() { - const user1 = User(id: '1', email: 'test@test.com'); - const user2 = User(id: '2', email: 'test2@test.com'); - group('AppEvent', () { - group('AppUserChanged', () { - test('supports value comparison', () { - expect( - const AppUserChanged(user: user1), - const AppUserChanged(user: user1), - ); - }); - - test('two different instances with different users are not equal', () { - expect( - const AppUserChanged(user: user1), - isNot(const AppUserChanged(user: user2)), - ); - }); - }); - - group(AppDownForMaintenanceStatusChanged, () { - test('supports comparison', () { - const appDown1 = - AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); - const appDown2 = - AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); - - expect(appDown1, equals(appDown2)); - }); - - test('two different instances with different values are not equal', () { - const appDown1 = - AppDownForMaintenanceStatusChanged(isDownForMaintenance: true); - const appDown2 = - AppDownForMaintenanceStatusChanged(isDownForMaintenance: false); - - expect(appDown1, isNot(appDown2)); - }); - }); - - test('AppLogoutRequested supports comparison', () { - const appLogoutRequested1 = AppLogoutRequested(); - const appLogoutRequested2 = AppLogoutRequested(); - - expect(appLogoutRequested1.props, equals([])); - expect(appLogoutRequested1, equals(appLogoutRequested2)); - }); - }); -} diff --git a/todo/test/app/bloc/app_state_test.dart b/todo/test/app/bloc/app_state_test.dart deleted file mode 100644 index 93f1fa8..0000000 --- a/todo/test/app/bloc/app_state_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -void main() { - const testUser1 = User(id: 'test1', email: 'test@test.com'); - const testUser2 = User(id: 'test2', email: 'test2@test.com'); - - group('AppState', () { - test('supports value comparison', () { - expect( - const AppState(user: testUser1, status: AppStatus.authenticated), - const AppState(user: testUser1, status: AppStatus.authenticated), - ); - }); - - test('two different instances with different users are not equal', () { - expect( - const AppState(user: testUser1, status: AppStatus.authenticated), - isNot(const AppState(user: testUser2, status: AppStatus.authenticated)), - ); - }); - - test('AppState copyWith', () { - const appState1 = AppState.authenticated( - user: testUser1, - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ); - final appState2 = appState1.copyWith(user: testUser2); - - expect(appState1.user, equals(testUser1)); - expect(appState2.user, equals(testUser2)); - }); - }); -} diff --git a/todo/test/app/extensions/build_context_test.dart b/todo/test/app/extensions/build_context_test.dart deleted file mode 100644 index dd904b6..0000000 --- a/todo/test/app/extensions/build_context_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - late AppBloc mockAppBloc; - - setUp(() { - mockAppBloc = MockAppBloc(); - }); - - group('BuildContextX', () { - testWidgets('user getter returns the user from AppBloc state', - (tester) async { - // Mock user - const testUser = User(id: 'id', email: 'email'); - - // Mock AppBloc state - when(() => mockAppBloc.state).thenReturn( - const AppState.authenticated( - user: testUser, - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ); - - late User? actualUser; - - await tester.pumpWidget( - BlocProvider.value( - value: mockAppBloc, - child: Builder( - builder: (context) { - actualUser = context.user; - return Container(); - }, - ), - ), - ); - - // Verify that the extension method returns the correct user - expect(actualUser, equals(testUser)); - }); - }); -} diff --git a/todo/test/app/view/app_view_test.dart b/todo/test/app/view/app_view_test.dart deleted file mode 100644 index 5ac3430..0000000 --- a/todo/test/app/view/app_view_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:async'; - -import 'package:api_client/api_client.dart'; -import 'package:app_config_repository/app_config_repository.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/app/app.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../helpers/helpers.dart'; - -class MockUserRepository extends Mock implements UserRepository { - @override - Stream get user => Stream.value(null); -} - -void main() { - late UserRepository mockUserRepository; - late GoRouter mockRouterConfig; - late AppBloc mockAppBloc; - late ApiClient mockApiClient; - late AppConfigRepository mockAppConfigRepository; - const testUser = User(id: 'id', email: 'email'); - - setUp(() { - mockUserRepository = MockUserRepository(); - mockRouterConfig = MockRouterConfig(); - mockAppBloc = MockAppBloc(); - mockApiClient = MockApiClient(); - mockAppConfigRepository = AppConfigRepository( - buildNumber: 1, - platform: Platform.android.isAndroid ? Platform.android : Platform.iOS, - apiClient: mockApiClient, - ); - when(() => mockAppBloc.state).thenReturn( - const AppState.unauthenticated( - forceUpgrade: ForceUpgrade(isUpgradeRequired: false), - isDownForMaintenance: false, - ), - ); - }); - - group('App', () { - testWidgets('AppWidget should build correctly', - (WidgetTester tester) async { - await tester.pumpWidget( - App( - userRepository: mockUserRepository, - user: testUser, - appConfigRepository: mockAppConfigRepository, - ), - ); - - expect(find.byType(MultiRepositoryProvider), findsOneWidget); - expect(find.byType(BlocProvider), findsOneWidget); - expect(find.byType(AppView), findsOneWidget); - }); - - testWidgets('AppView should build correctly', (WidgetTester tester) async { - await tester.pumpApp( - AppView( - routerConfig: mockRouterConfig, - ), - appBloc: mockAppBloc, - ); - - expect(find.byType(AppView), findsOneWidget); - }); - }); -} diff --git a/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart b/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart deleted file mode 100644 index 1942ec1..0000000 --- a/todo/test/authentication/delete_account/bloc/delete_account_bloc_test.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late UserRepository mockUserRepository; - - setUp(() { - mockUserRepository = MockUserRepository(); - }); - group('DeleteAccountBloc', () { - blocTest( - 'emit [] when nothing is added', - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - expect: () => [], - ); - - blocTest( - 'emits [email] when ', - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountEmailChanged('email')), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(email: const Email.dirty('email')), - ], - ); - - blocTest( - 'emits [password] when DeleteAccountPasswordChanged is added', - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountPasswordChanged('password')), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(password: const Password.dirty('password')), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.loading] ' - 'and [DeleteAccountStatus.reauthFailure] ' - 'when DeleteAccountRequested is added', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenThrow(UserReAuthenticateFailure(Error(), StackTrace.empty)); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountRequested()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.reauthFailure), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.failure] ' - 'when DeleteAccountRequested is added and throws an exception', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenThrow(Exception()); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountRequested()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.failure), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.loading] ' - 'and [DeleteAccountStatus.success] ' - 'when DeleteAccountSubmitted is added', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenAnswer((_) async {}); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountSubmitted()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.success), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.deleteAccountFailure] ' - 'when DeleteAccountSubmitted is added and throws an exception', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenThrow(UserDeleteAccountFailure(Error(), StackTrace.empty)); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountSubmitted()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.deleteAccountFailure), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.failure] ' - 'when DeleteAccountSubmitted is added and throws an exception', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenThrow(Exception()); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountSubmitted()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.failure), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.reauthenticated] ' - 'when deleteAccount is succesful and DeleteAccountRequested is added', - setUp: () { - when( - () => mockUserRepository.deleteAccount(), - ).thenAnswer((_) async {}); - }, - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountRequested()), - expect: () => [ - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.loading), - const DeleteAccountState.initial() - .copyWith(status: DeleteAccountStatus.reauthenticated), - ], - ); - - blocTest( - 'emits [DeleteAccountStatus.initial] ' - 'when DeleteAccountResetted is added', - build: () => DeleteAccountBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const DeleteAccountResetted()), - expect: () => [ - const DeleteAccountState.initial(), - ], - ); - }); -} diff --git a/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart b/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart deleted file mode 100644 index c2f7d44..0000000 --- a/todo/test/authentication/delete_account/bloc/delete_account_event_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; - -void main() { - group('DeleteAccountEvent', () { - group('DeleteAccountEmailChanged', () { - test('supports value comparison', () { - expect( - const DeleteAccountEmailChanged('email'), - const DeleteAccountEmailChanged('email'), - ); - }); - - test('two different instances with different emails are not equal', () { - expect( - const DeleteAccountEmailChanged('email1'), - isNot(const DeleteAccountEmailChanged('email2')), - ); - }); - }); - - group('DeleteAccountPasswordChanged', () { - test('supports value comparison', () { - expect( - const DeleteAccountPasswordChanged('password'), - const DeleteAccountPasswordChanged('password'), - ); - }); - - test('two different instances with different passwords are not equal', - () { - expect( - const DeleteAccountPasswordChanged('password1'), - isNot(const DeleteAccountPasswordChanged('password2')), - ); - }); - }); - - group('DeleteAccountRequested', () { - test('supports value comparison', () { - expect(const DeleteAccountRequested().props, []); - expect(const DeleteAccountRequested(), const DeleteAccountRequested()); - }); - }); - - group('DeleteAccountSubmitted', () { - test('supports value comparison', () { - expect(const DeleteAccountSubmitted().props, []); - expect(const DeleteAccountSubmitted(), const DeleteAccountSubmitted()); - }); - }); - - group('DeleteAccountResetted', () { - test('supports value comparison', () { - expect(const DeleteAccountResetted().props, []); - expect(const DeleteAccountResetted(), const DeleteAccountResetted()); - }); - }); - }); -} diff --git a/todo/test/authentication/delete_account/view/delete_account_page_test.dart b/todo/test/authentication/delete_account/view/delete_account_page_test.dart deleted file mode 100644 index 23c6efd..0000000 --- a/todo/test/authentication/delete_account/view/delete_account_page_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - group('DeleteAccountPage', () { - test('is a page', () { - expect(const DeleteAccountPage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = const DeleteAccountPage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [DeleteAccountView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - const DeleteAccountPage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(DeleteAccountView), findsOneWidget); - }); -} diff --git a/todo/test/authentication/delete_account/view/delete_account_view_test.dart b/todo/test/authentication/delete_account/view/delete_account_view_test.dart deleted file mode 100644 index 7f14f42..0000000 --- a/todo/test/authentication/delete_account/view/delete_account_view_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo_ui/todo_ui.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late DeleteAccountBloc mockDeleteAccountBloc; - - setUp(() { - mockDeleteAccountBloc = MockDeleteAccountBloc(); - when(() => mockDeleteAccountBloc.state) - .thenReturn(const DeleteAccountState.initial()); - }); - group('DeleteAccountView', () { - testWidgets('renders correctly', (WidgetTester tester) async { - await tester.pumpApp( - const DeleteAccountView(), - deleteAccountBloc: mockDeleteAccountBloc, - ); - - expect(find.byType(DeleteAccountForm), findsOneWidget); - }); - - testWidgets('delete account button adds [DeleteAccountSubmitted] event', - (WidgetTester tester) async { - when(() => mockDeleteAccountBloc.state).thenReturn( - const DeleteAccountState.initial().copyWith( - email: const Email.dirty('email@email.com'), - password: const Password.dirty('Password123!'), - ), - ); - await tester.pumpApp( - const DeleteAccountView(), - deleteAccountBloc: mockDeleteAccountBloc, - ); - - final submitButton = find.byType(UIOutlinedButton); - await tester.tap(submitButton); - - verify(() => mockDeleteAccountBloc.add(const DeleteAccountSubmitted())) - .called(1); - }); - - testWidgets( - 'email text field adds [DeleteAccountEmailChanged] onChanged', - (tester) async { - await tester.pumpApp( - const DeleteAccountView(), - deleteAccountBloc: mockDeleteAccountBloc, - ); - - final emailField = find.byKey(const Key('DeleteAccountEmail'),); - await tester.enterText(emailField, 'email'); - - verify( - () => mockDeleteAccountBloc.add( - const DeleteAccountEmailChanged('email'), - ), - ).called(1); - }, - ); - - testWidgets( - 'password text field adds [DeleteAccountPasswordChanged] onChanged', - (tester) async { - await tester.pumpApp( - const DeleteAccountView(), - deleteAccountBloc: mockDeleteAccountBloc, - ); - - final passwordField = find.byKey(const Key('DeleteAccountPassword'),); - await tester.enterText(passwordField, 'password'); - - verify( - () => mockDeleteAccountBloc.add( - const DeleteAccountPasswordChanged('password'), - ), - ).called(1); - }, - ); - }); -} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart deleted file mode 100644 index 985d0f3..0000000 --- a/todo/test/authentication/forgot_password/bloc/forgot_password_bloc_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late UserRepository mockUserRepository; - - setUp(() { - mockUserRepository = MockUserRepository(); - }); - - group('ForgotPasswordBloc', () { - blocTest( - 'emits [] when nothing is added', - build: () => ForgotPasswordBloc(userRepository: mockUserRepository), - expect: () => [], - ); - - blocTest( - 'emits [email] when ForgotPasswordEmailChanged is added', - build: () => ForgotPasswordBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const ForgotPasswordEmailChanged('email')), - expect: () => [ - const ForgotPasswordState.initial() - .copyWith(email: const Email.dirty('email')), - ], - ); - - blocTest( - 'emits [ForgotPasswordStatus.loading] ' - 'and [ForgotPasswordStatus.success] ' - 'when ForgotPasswordSubmitted is added', - setUp: () { - when( - () => mockUserRepository.forgotPassword(email: any(named: 'email')), - ).thenAnswer((_) async {}); - }, - build: () => ForgotPasswordBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const ForgotPasswordSubmitted()), - expect: () => [ - const ForgotPasswordState.initial().copyWith( - status: ForgotPasswordStatus.loading, - ), - const ForgotPasswordState.initial().copyWith( - status: ForgotPasswordStatus.success, - ), - ], - ); - - blocTest( - 'emits [ForgotPasswordStatus.loading] ' - 'and [ForgotPasswordStatus.error] ' - 'when ForgotPasswordSubmitted is added', - setUp: () { - when( - () => mockUserRepository.forgotPassword(email: any(named: 'email')), - ).thenThrow(Exception()); - }, - build: () => ForgotPasswordBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const ForgotPasswordSubmitted()), - expect: () => [ - const ForgotPasswordState.initial().copyWith( - status: ForgotPasswordStatus.loading, - ), - const ForgotPasswordState.initial().copyWith( - status: ForgotPasswordStatus.error, - ), - ], - ); - }); -} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart deleted file mode 100644 index 05e6e7b..0000000 --- a/todo/test/authentication/forgot_password/bloc/forgot_password_event_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; - -void main() { - const testEmail1 = 'test1@example.com'; - const testEmail2 = 'test2@example.com'; - - group('ForgotPasswordSubmitted', () { - test('ForgotPasswordSubmitted supports value comparison', () { - expect( - const ForgotPasswordSubmitted(), - const ForgotPasswordSubmitted(), - ); - expect( - const ForgotPasswordSubmitted().props, - const [], - ); - }); - }); - - group('ForgotPasswordEmailChanged', () { - test('supports value comparison', () { - expect( - const ForgotPasswordEmailChanged(testEmail1), - const ForgotPasswordEmailChanged(testEmail1), - ); - }); - - test('two different instances with different emails are not equal', () { - expect( - const ForgotPasswordEmailChanged(testEmail1), - isNot(const ForgotPasswordEmailChanged(testEmail2)), - ); - }); - }); -} diff --git a/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart b/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart deleted file mode 100644 index d4958f9..0000000 --- a/todo/test/authentication/forgot_password/bloc/forgot_password_state_test.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; - -void main() { - const validEmail = Email.dirty('test@example.com'); - const invalidEmail = Email.dirty('invalid'); - - group('ForgotPasswordState', () { - test('supports value comparison', () { - const state1 = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: Email.pure(), - ); - - const state2 = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: Email.pure(), - ); - - expect(state1, state2); - }); - - test('copyWith method creates new instance with replaced values', () { - const originalState = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: Email.pure(), - ); - - final modifiedState = originalState.copyWith( - status: ForgotPasswordStatus.loading, - email: validEmail, - ); - - expect(modifiedState.status, ForgotPasswordStatus.loading); - expect(modifiedState.email.value, validEmail.value); - }); - - test('valid getter returns true when email is valid', () { - const state = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: validEmail, - ); - - expect(state.valid, true); - }); - - test('valid getter returns false when email is invalid', () { - const state = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: invalidEmail, - ); - - expect(state.valid, false); - }); - - test('props list contains status and email', () { - const state = ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: validEmail, - ); - - expect(state.props, [ - ForgotPasswordStatus.initial, - validEmail, - ]); - }); - - test('isInitial returns true when status is initial', () { - const status = ForgotPasswordStatus.initial; - expect(status.isInitial, isTrue); - expect(status.isLoading, isFalse); - expect(status.isLoaded, isFalse); - }); - - test('isLoading returns true when status is loading', () { - const status = ForgotPasswordStatus.loading; - expect(status.isInitial, isFalse); - expect(status.isLoading, isTrue); - expect(status.isLoaded, isFalse); - }); - - test('isLoaded returns true when status is loaded', () { - const status = ForgotPasswordStatus.loaded; - expect(status.isInitial, isFalse); - expect(status.isLoading, isFalse); - expect(status.isLoaded, isTrue); - }); - - test('initial constructor sets status to initial and email to pure', () { - const state = ForgotPasswordState.initial(); - expect(state.status, ForgotPasswordStatus.initial); - expect(state.email, const Email.pure()); - }); - - test('supports value comparision', () { - expect( - const ForgotPasswordState.initial(), - const ForgotPasswordState.initial(), - ); - }); - - test('test empty copyWith', () { - expect( - const ForgotPasswordState.initial().copyWith(), - const ForgotPasswordState.initial(), - ); - }); - - test('test initial constructor', () { - expect( - const ForgotPasswordState.initial(), - const ForgotPasswordState( - status: ForgotPasswordStatus.initial, - email: Email.pure(), - ), - ); - }); - }); -} diff --git a/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart b/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart deleted file mode 100644 index c525c9e..0000000 --- a/todo/test/authentication/forgot_password/view/forgot_password_view_test.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late ForgotPasswordBloc mockForgotPasswordBloc; - - setUp(() { - mockForgotPasswordBloc = MockForgotPasswordBloc(); - when(() => mockForgotPasswordBloc.state) - .thenReturn(const ForgotPasswordState.initial()); - }); - - group('SignUpView', () { - testWidgets('renders correctly', (WidgetTester tester) async { - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - expect(find.byType(TextField), findsNWidgets(1)); - expect(find.byType(ElevatedButton), findsOneWidget); - }); - - testWidgets('shows success snackbar when reset is successful', - (WidgetTester tester) async { - whenListen( - mockForgotPasswordBloc, - Stream.fromIterable([ - const ForgotPasswordState.initial() - .copyWith(status: ForgotPasswordStatus.success), - ]), - ); - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('shows error snackbar when reset fails', - (WidgetTester tester) async { - whenListen( - mockForgotPasswordBloc, - Stream.fromIterable([ - const ForgotPasswordState.initial() - .copyWith(status: ForgotPasswordStatus.error), - ]), - ); - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('reset button is disabled when form is invalid', - (WidgetTester tester) async { - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - await tester.pumpAndSettle(); - final resetButton = find.byType(ElevatedButton); - expect(tester.widget(resetButton).enabled, isFalse); - }); - - testWidgets('reset button is enabled when form is valid', - (WidgetTester tester) async { - whenListen( - mockForgotPasswordBloc, - Stream.fromIterable([ - const ForgotPasswordState.initial() - .copyWith(email: const Email.dirty('mail@mail.com')), - ]), - ); - - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - final resetButton = find.byType(ElevatedButton); - expect(tester.widget(resetButton).enabled, isTrue); - }); - - testWidgets('should call [ForgotPasswordSubmitted] on reset button press', - (WidgetTester tester) async { - when(() => mockForgotPasswordBloc.state).thenReturn( - const ForgotPasswordState.initial().copyWith( - email: const Email.dirty('test@example.com'), - ), - ); - - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - final resetButton = find.byType(ElevatedButton); - await tester.tap(resetButton); - - verify( - () => mockForgotPasswordBloc.add( - const ForgotPasswordSubmitted(), - ), - ).called(1); - }); - - testWidgets('should call [ForgotPasswordEmailChanged] on email change', - (WidgetTester tester) async { - await tester.pumpApp( - const ForgotPasswordView(), - forgotPasswordBloc: mockForgotPasswordBloc, - ); - - final emailField = find.byType(TextField); - await tester.enterText(emailField, 'test@example.com'); - - verify( - () => mockForgotPasswordBloc.add( - const ForgotPasswordEmailChanged('test@example.com'), - ), - ).called(1); - }); - }); -} diff --git a/todo/test/authentication/login/bloc/login_bloc_test.dart b/todo/test/authentication/login/bloc/login_bloc_test.dart deleted file mode 100644 index c816dc6..0000000 --- a/todo/test/authentication/login/bloc/login_bloc_test.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/login/login.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late UserRepository mockUserRepository; - - setUp(() { - mockUserRepository = MockUserRepository(); - }); - group('LoginBloc', () { - blocTest( - 'emits [] when nothing is added', - build: () => LoginBloc(userRepository: mockUserRepository), - expect: () => [], - ); - - blocTest( - 'emits [email] when LoginEmailChanged is added', - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const LoginEmailChanged('email')), - expect: () => [ - const LoginState.initial().copyWith(email: const Email.dirty('email')), - ], - ); - - blocTest( - 'emits [password] when LoginPasswordChanged is added', - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const LoginPasswordChanged('password')), - expect: () => [ - const LoginState.initial() - .copyWith(password: const Password.dirty('password')), - ], - ); - - blocTest( - 'emits [obscure] as true when LoginPasswordVisibilityChanged is added', - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => - bloc.add(const LoginPasswordVisibilityChanged(obscure: true)), - expect: () => [ - const LoginState.initial().copyWith(obscurePassword: true), - ], - ); - - blocTest( - 'emits [obscure] as false when LoginPasswordVisibilityChanged is added', - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => - bloc.add(const LoginPasswordVisibilityChanged(obscure: false)), - expect: () => [ - const LoginState.initial().copyWith(obscurePassword: false), - ], - ); - - blocTest( - 'emits [FormzSubmissionStatus.inProgress] ' - 'and [FormzSubmissionStatus.success] ' - 'when LoginWithEmailAndPasswordRequested is added', - setUp: () { - when( - () => mockUserRepository.signIn( - email: any(named: 'email'), - password: any(named: 'password'), - ), - ).thenAnswer((_) async {}); - }, - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const LoginWithEmailAndPasswordRequested()), - expect: () => [ - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.inProgress), - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.success), - ], - ); - - blocTest( - 'emits [FormzSubmissionStatus.inProgress] ' - 'and [FormzSubmissionStatus.failure] ' - 'when LoginWithEmailAndPasswordRequested is added', - setUp: () { - when( - () => mockUserRepository.signIn( - email: any(named: 'email'), - password: any(named: 'password'), - ), - ).thenThrow(Exception()); - }, - build: () => LoginBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const LoginWithEmailAndPasswordRequested()), - expect: () => [ - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.inProgress), - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.failure), - ], - ); - }); -} diff --git a/todo/test/authentication/login/bloc/login_event_test.dart b/todo/test/authentication/login/bloc/login_event_test.dart deleted file mode 100644 index a800d70..0000000 --- a/todo/test/authentication/login/bloc/login_event_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/login/login.dart'; - -void main() { - group('LoginEvent', () { - group('LoginEmailChanged', () { - test('supports value comparison', () { - expect( - const LoginEmailChanged('test@example.com'), - const LoginEmailChanged('test@example.com'), - ); - }); - - test('two different instances with different emails are not equal', () { - expect( - const LoginEmailChanged('test1@example.com'), - isNot(const LoginEmailChanged('test2@example.com')), - ); - }); - }); - - group('LoginPasswordChanged', () { - test('supports value comparison', () { - expect( - const LoginPasswordChanged('password123'), - const LoginPasswordChanged('password123'), - ); - }); - - test('two different instances with different passwords are not equal', - () { - expect( - const LoginPasswordChanged('password1'), - isNot(const LoginPasswordChanged('password2')), - ); - }); - }); - - group('LoginWithEmailAndPasswordRequested', () { - test('supports value comparison', () { - expect( - const LoginWithEmailAndPasswordRequested().props, - equals([]), - ); - expect( - const LoginWithEmailAndPasswordRequested(), - const LoginWithEmailAndPasswordRequested(), - ); - }); - }); - - group('LoginPasswordVisibilityChanged', () { - test('supports value comparison', () { - expect( - const LoginPasswordVisibilityChanged(obscure: true), - const LoginPasswordVisibilityChanged(obscure: true), - ); - }); - - test( - 'two different instances with different obscure values are not equal', - () { - expect( - const LoginPasswordVisibilityChanged(obscure: true), - isNot(const LoginPasswordVisibilityChanged(obscure: false)), - ); - }); - }); - }); -} diff --git a/todo/test/authentication/login/view/forgot_password_page_test.dart b/todo/test/authentication/login/view/forgot_password_page_test.dart deleted file mode 100644 index a16095a..0000000 --- a/todo/test/authentication/login/view/forgot_password_page_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - group('ForgotPasswordPage', () { - test('is a page', () { - expect(ForgotPasswordPage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = ForgotPasswordPage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [ForgotPasswordView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - ForgotPasswordPage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(ForgotPasswordView), findsOneWidget); - }); -} diff --git a/todo/test/authentication/login/view/login_view_test.dart b/todo/test/authentication/login/view/login_view_test.dart deleted file mode 100644 index f021cd0..0000000 --- a/todo/test/authentication/login/view/login_view_test.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/login/login.dart'; -import 'package:todo_ui/todo_ui.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late LoginBloc mockLoginBloc; - late NavigatorObserver mockNavigatorObserver; - - setUpAll(() { - registerFallbackValue(FakeRoute()); - }); - - setUp(() { - mockLoginBloc = MockLoginBloc(); - mockNavigatorObserver = MockNavigatorObserver(); - - when(() => mockLoginBloc.state).thenReturn(const LoginState.initial()); - }); - - group('LoginView', () { - testWidgets('renders correctly', (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - expect(find.byType(TextField), findsNWidgets(2)); - expect(find.byType(UIOutlinedButton), findsOneWidget); - }); - - testWidgets('shows success snackbar when sign up is successful', - (WidgetTester tester) async { - whenListen( - mockLoginBloc, - Stream.fromIterable([ - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.success), - ]), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('shows error snackbar when sign up fails', - (WidgetTester tester) async { - whenListen( - mockLoginBloc, - Stream.fromIterable([ - const LoginState.initial() - .copyWith(status: FormzSubmissionStatus.failure), - ]), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('login button is disabled when form is invalid', - (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - await tester.pumpAndSettle(); - final loginButton = find.byType(OutlinedButton); - expect(tester.widget(loginButton).enabled, isFalse); - }); - - testWidgets('login button is enabled when form is valid', - (WidgetTester tester) async { - when(() => mockLoginBloc.state).thenReturn( - const LoginState.initial().copyWith( - email: const Email.dirty('email@mail.com'), - password: const Password.dirty('Test12345.'), - ), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final signUpButton = find.byType(OutlinedButton); - expect(tester.widget(signUpButton).enabled, isTrue); - }); - - testWidgets( - 'adds [LoginWithEmailAndPasswordRequested]' - ' when login button is pressed', (WidgetTester tester) async { - when(() => mockLoginBloc.state).thenReturn( - const LoginState.initial().copyWith( - email: const Email.dirty('email@mail.com'), - password: const Password.dirty('Test12345.'), - ), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final loginButton = find.byType(OutlinedButton); - await tester.tap(loginButton); - verify( - () => mockLoginBloc.add(const LoginWithEmailAndPasswordRequested()), - ).called(1); - }); - - testWidgets( - 'adds [LoginPasswordChanged] when password textfield is changed', - (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final passwordTextField = find.byKey( - const Key('LoginPasswordTextField'), - ); - await tester.enterText(passwordTextField, 'Test12345.'); - - verify( - () => mockLoginBloc.add( - const LoginPasswordChanged('Test12345.'), - ), - ).called(1); - }); - - testWidgets( - 'changes visibility when [PasswordTextField] icon ' - ' is tapped', (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final passwordTextField = find.byKey( - const Key('LoginPasswordTextField'), - ); - - await tester.tap( - find.descendant( - of: passwordTextField, - matching: find.byType(IconButton), - ), - ); - - verify( - () => mockLoginBloc.add( - const LoginPasswordVisibilityChanged(obscure: false), - ), - ).called(1); - }); - - testWidgets('[EmailTextField] should display error state', - (WidgetTester tester) async { - when(() => mockLoginBloc.state).thenReturn( - const LoginState.initial().copyWith( - email: const Email.dirty('asd'), - ), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final emailTextField = find - .byKey(const Key('LoginEmailTextField')) - .evaluate() - .where((element) => element.widget is UITextField) - .single; - - expect(emailTextField, isNotNull); - - await tester.pump(); - - final textFieldWidget = emailTextField.widget as UITextField; - expect(textFieldWidget.errorText, isNotNull); - }); - - testWidgets('[EmailTextField] should add [LoginEmailChanged] event', - (WidgetTester tester) async { - when(() => mockLoginBloc.state).thenReturn( - const LoginState.initial().copyWith( - email: const Email.dirty('asd'), - ), - ); - - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - ); - - final emailTextField = find.byKey( - const Key('LoginEmailTextField'), - ); - await tester.enterText(emailTextField, 'email'); - - verify( - () => mockLoginBloc.add( - const LoginEmailChanged('email'), - ), - ).called(1); - }); - - testWidgets('[ForgotPasswordButton] navigates on pressed', - (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - navigatorObserver: mockNavigatorObserver, - ); - - final forgotPasswordButton = find.byKey( - const Key('LoginForgotPasswordButton'), - ); - await tester.tap(forgotPasswordButton); - verify(() => mockNavigatorObserver.didPush(any(), any())); - }); - - testWidgets('[SignUpButton] navigates on pressed', - (WidgetTester tester) async { - await tester.pumpApp( - const LoginView(), - loginBloc: mockLoginBloc, - navigatorObserver: mockNavigatorObserver, - ); - - final signUpButton = find.byKey( - const Key('LoginSignUpButton'), - ); - await tester.tap(signUpButton); - verify(() => mockNavigatorObserver.didPush(any(), any())); - }); - }); -} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart deleted file mode 100644 index d7cf135..0000000 --- a/todo/test/authentication/sign_up/bloc/sign_up_bloc_test.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:user_repository/user_repository.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late UserRepository mockUserRepository; - - setUp(() { - mockUserRepository = MockUserRepository(); - }); - - group('SignUpBloc', () { - blocTest( - 'emits [] when nothing is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - expect: () => [], - ); - - blocTest( - 'emits [name] when SignUpNameChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const SignUpNameChanged('name')), - expect: () => [ - const SignUpState.initial().copyWith(name: const Name.dirty('name')), - ], - ); - - blocTest( - 'emits [email] when SignUpEmailChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const SignUpEmailChanged('email')), - expect: () => [ - const SignUpState.initial().copyWith(email: const Email.dirty('email')), - ], - ); - - blocTest( - 'emits [password] when SignUpPasswordChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => - bloc.add(const SignUpPasswordChanged(password: 'password')), - expect: () => [ - const SignUpState.initial() - .copyWith(password: const Password.dirty('password')), - ], - ); - - blocTest( - 'emits [confirmationPassword] when ' - 'SignUpConfirmationPasswordChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add( - const SignUpConfirmationPasswordChanged( - password: 'confirmationPassword', - ), - ), - expect: () => [ - const SignUpState.initial().copyWith( - confirmationPassword: 'confirmationPassword', - ), - ], - ); - - blocTest( - 'emits [obscurePasswords] as true ' - 'when SignUpPasswordVisibilityChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => - bloc.add(const SignUpPasswordVisibilityChanged(obscure: true)), - expect: () => [ - const SignUpState.initial().copyWith( - obscurePasswords: true, - ), - ], - ); - - blocTest( - 'emits [obscurePasswords] as false ' - 'when SignUpPasswordVisibilityChanged is added', - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => - bloc.add(const SignUpPasswordVisibilityChanged(obscure: false)), - expect: () => [ - const SignUpState.initial().copyWith( - obscurePasswords: false, - ), - ], - ); - - blocTest( - 'emits [FormzSubmissionStatus.inProgress] ' - 'and [FormzSubmissionStatus.success] ' - 'when SignUpWithEmailAndPasswordRequested is added', - setUp: () { - when( - () => mockUserRepository.signUp( - email: any(named: 'email'), - password: any(named: 'password'), - name: any(named: 'name'), - ), - ).thenAnswer((_) async {}); - }, - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const SignUpWithEmailAndPasswordRequested()), - expect: () => [ - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.inProgress), - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.success), - ], - ); - - blocTest( - 'emits [FormzSubmissionStatus.inProgress] ' - 'and [FormzSubmissionStatus.failure] ' - 'when SignUpWithEmailAndPasswordRequested is added', - setUp: () { - when( - () => mockUserRepository.signUp( - email: any(named: 'email'), - password: any(named: 'password'), - name: any(named: 'name'), - ), - ).thenThrow(Exception()); - }, - build: () => SignUpBloc(userRepository: mockUserRepository), - act: (bloc) => bloc.add(const SignUpWithEmailAndPasswordRequested()), - expect: () => [ - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.inProgress), - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.failure), - ], - ); - }); -} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart deleted file mode 100644 index fa25c74..0000000 --- a/todo/test/authentication/sign_up/bloc/sign_up_event_test.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; - -void main() { - const testEmail1 = 'test1@example.com'; - const testEmail2 = 'test2@example.com'; - const testPassword1 = 'password1'; - const testPassword2 = 'password2'; - const testName1 = 'John Doe'; - const testName2 = 'Jane Doe'; - - group('SignUpEvent', () { - group('SignUpEmailChanged', () { - test('supports value comparison', () { - expect( - const SignUpEmailChanged(testEmail1), - const SignUpEmailChanged(testEmail1), - ); - }); - - test('two different instances with different emails are not equal', () { - expect( - const SignUpEmailChanged(testEmail1), - isNot(const SignUpEmailChanged(testEmail2)), - ); - }); - }); - - group('SignUpPasswordChanged', () { - test('supports value comparison', () { - expect( - const SignUpPasswordChanged(password: testPassword1), - const SignUpPasswordChanged(password: testPassword1), - ); - }); - - test('two different instances with different passwords are not equal', - () { - expect( - const SignUpPasswordChanged(password: testPassword1), - isNot(const SignUpPasswordChanged(password: testPassword2)), - ); - }); - }); - - group('SignUpConfirmationPasswordChanged', () { - test('supports value comparison', () { - expect( - const SignUpConfirmationPasswordChanged(password: testPassword1), - const SignUpConfirmationPasswordChanged(password: testPassword1), - ); - }); - - test('two different instances with different passwords are not equal', - () { - expect( - const SignUpConfirmationPasswordChanged(password: testPassword1), - isNot( - const SignUpConfirmationPasswordChanged(password: testPassword2), - ), - ); - }); - }); - - group('SignUpWithEmailAndPasswordRequested', () { - test('supports value comparison', () { - expect( - const SignUpWithEmailAndPasswordRequested(), - const SignUpWithEmailAndPasswordRequested(), - ); - }); - }); - - group('SignUpPasswordVisibilityChanged', () { - test('supports value comparison', () { - expect( - const SignUpPasswordVisibilityChanged(obscure: true), - const SignUpPasswordVisibilityChanged(obscure: true), - ); - }); - - test( - 'two different instances with different obscure values are not equal', - () { - expect( - const SignUpPasswordVisibilityChanged(obscure: true), - isNot(const SignUpPasswordVisibilityChanged(obscure: false)), - ); - }); - }); - - group('SignUpNameChanged', () { - test('supports value comparison', () { - expect( - const SignUpNameChanged(testName1), - const SignUpNameChanged(testName1), - ); - }); - - test('two different instances with different names are not equal', () { - expect( - const SignUpNameChanged(testName1), - isNot(const SignUpNameChanged(testName2)), - ); - }); - }); - }); -} diff --git a/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart b/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart deleted file mode 100644 index 978572e..0000000 --- a/todo/test/authentication/sign_up/bloc/sign_up_state_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; - -void main() { - group('SignUpState', () { - const name = Name.dirty('John Doe'); - const invalidPassword = Password.dirty('password123'); - const validPassword = Password.dirty('Password123!'); - const validPasswordValue = 'Password123!'; - const email = Email.dirty('test@example.com'); - - test('supports value comparison', () { - const state1 = SignUpState.initial(); - const state2 = SignUpState.initial(); - - expect(state1, state2); - }); - - test('copyWith method creates new instance with replaced values', () { - const originalState = SignUpState.initial(); - final modifiedState = originalState.copyWith( - status: FormzSubmissionStatus.success, - email: email, - password: validPassword, - name: name, - confirmationPassword: validPassword.value, - obscurePasswords: false, - ); - - expect(modifiedState.status, FormzSubmissionStatus.success); - expect(modifiedState.email.value, email.value); - expect(modifiedState.password.value, validPassword.value); - expect(modifiedState.name.value, name.value); - expect(modifiedState.confirmationPassword, validPassword.value); - expect(modifiedState.obscurePasswords, false); - }); - - test('valid getter returns true when all fields are valid', () { - const state = SignUpState( - status: FormzSubmissionStatus.success, - email: email, - password: validPassword, - name: name, - confirmationPassword: validPasswordValue, - obscurePasswords: false, - ); - - expect(state.valid, true); - }); - - test('valid getter returns false when any field is invalid', () { - const state = SignUpState( - status: FormzSubmissionStatus.initial, - email: email, - password: invalidPassword, - name: Name.pure(), - confirmationPassword: validPasswordValue, - obscurePasswords: false, - ); - - expect(state.valid, false); - }); - - test('props list contains all state properties', () { - const state = SignUpState( - status: FormzSubmissionStatus.initial, - email: email, - password: validPassword, - name: name, - confirmationPassword: validPasswordValue, - obscurePasswords: false, - ); - - expect(state.props, [ - FormzSubmissionStatus.initial, - email, - validPassword, - validPasswordValue, - false, - name, - ]); - }); - }); -} diff --git a/todo/test/authentication/sign_up/view/sign_up_page_test.dart b/todo/test/authentication/sign_up/view/sign_up_page_test.dart deleted file mode 100644 index 636a57e..0000000 --- a/todo/test/authentication/sign_up/view/sign_up_page_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/authentication/sign_up/view/view.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - group('SignUpPage', () { - test('is a page', () { - expect(const SignUpPage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = const SignUpPage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [SignUpView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - const SignUpPage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(SignUpView), findsOneWidget); - }); -} diff --git a/todo/test/authentication/sign_up/view/sign_up_view_test.dart b/todo/test/authentication/sign_up/view/sign_up_view_test.dart deleted file mode 100644 index aae743a..0000000 --- a/todo/test/authentication/sign_up/view/sign_up_view_test.dart +++ /dev/null @@ -1,399 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:form_inputs/form_inputs.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; - -import '../../../helpers/helpers.dart'; - -void main() { - late SignUpBloc mockSignUpBloc; - late NavigatorObserver mockNavigatorObserver; - - setUpAll(() { - registerFallbackValue(FakeRoute()); - }); - - setUp(() { - mockSignUpBloc = MockSignUpBloc(); - mockNavigatorObserver = MockNavigatorObserver(); - when(() => mockSignUpBloc.state).thenReturn(const SignUpState.initial()); - }); - - group('SignUpView', () { - testWidgets('renders correctly', (WidgetTester tester) async { - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - expect(find.byType(TextField), findsNWidgets(4)); - expect(find.byType(ElevatedButton), findsOneWidget); - }); - - testWidgets('shows success snackbar when sign up is successful', - (WidgetTester tester) async { - whenListen( - mockSignUpBloc, - Stream.fromIterable([ - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.success), - ]), - ); - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('pops screen when sign up is successful', - (WidgetTester tester) async { - whenListen( - mockSignUpBloc, - Stream.fromIterable([ - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.success), - ]), - ); - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => context.push( - '/signUpTestPath', - ), - ), - ); - }, - ), - signUpBloc: mockSignUpBloc, - navigatorObserver: mockNavigatorObserver, - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - - verify(() => mockNavigatorObserver.didPop(any(), any())); - }); - - testWidgets('shows error snackbar when sign up fails', - (WidgetTester tester) async { - whenListen( - mockSignUpBloc, - Stream.fromIterable([ - const SignUpState.initial() - .copyWith(status: FormzSubmissionStatus.failure), - ]), - ); - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - await tester.pumpAndSettle(); - expect(find.byType(ScaffoldMessenger), findsOneWidget); - }); - - testWidgets('signUp button is disabled when form is invalid', - (WidgetTester tester) async { - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - await tester.pumpAndSettle(); - final signUpButton = find.byType(ElevatedButton); - expect(tester.widget(signUpButton).enabled, isFalse); - }); - - testWidgets('signUp button is enabled when form is valid', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - email: const Email.dirty('email@mail.com'), - password: const Password.dirty('a123456789P.'), - confirmationPassword: 'a123456789P.', - name: const Name.dirty('name'), - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final signUpButton = find.byType(ElevatedButton); - expect(tester.widget(signUpButton).enabled, isTrue); - }); - - testWidgets('signUp button onPressed is called when pressed', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - email: const Email.dirty('email@mail.com'), - password: const Password.dirty('a123456789P.'), - confirmationPassword: 'a123456789P.', - name: const Name.dirty('name'), - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final signUpButton = find.byType(ElevatedButton); - await tester.tap(signUpButton); - verify( - () => mockSignUpBloc.add( - const SignUpWithEmailAndPasswordRequested(), - ), - ).called(1); - }); - - testWidgets('should change [NameTextField] value on change', - (WidgetTester tester) async { - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final nameTextFieldFinder = find.byKey( - const Key('SignUpNameTextField'), - ); - await tester.enterText(nameTextFieldFinder, 'name'); - expect(find.text('name'), findsOneWidget); - }); - - testWidgets('[NameTextField] should display error state', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - name: const Name.dirty('@#%^&*'), - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final nameTextFieldFinder = find.byKey( - const Key('SignUpNameTextField'), - ); - expect( - tester.widget(nameTextFieldFinder).decoration?.errorText, - isNotNull, - ); - }); - - testWidgets('should change [EmailTextField] value on change', - (WidgetTester tester) async { - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final emailTextFieldFinder = find.byKey( - const Key('SignUpEmailTextField'), - ); - await tester.enterText( - emailTextFieldFinder, - 'email@email.com', - ); - - expect(find.text('email@email.com'), findsOneWidget); - }); - - testWidgets('[EmailTextField] should display error state', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - email: const Email.dirty('email'), - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final emailTextFieldFinder = find.byKey( - const Key('SignUpEmailTextField'), - ); - expect( - tester.widget(emailTextFieldFinder).decoration?.errorText, - isNotNull, - ); - }); - - testWidgets('should change [PasswordTextField] value on change', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - obscurePasswords: false, - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final passwordTextFieldFinder = find.byKey( - const Key('SignUpPasswordTextField'), - ); - await tester.enterText( - passwordTextFieldFinder, - 'a123456789P.', - ); - verify( - () => mockSignUpBloc.add( - const SignUpPasswordChanged( - password: 'a123456789P.', - ), - ), - ).called(1); - }); - - testWidgets('[PasswordTextField] should display error state', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - password: const Password.dirty('password'), - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final passwordTextFieldFinder = find.byKey( - const Key('SignUpPasswordTextField'), - ); - expect( - tester.widget(passwordTextFieldFinder).decoration?.errorText, - isNotNull, - ); - }); - - testWidgets('should change [ConfirmationPasswordTextField] value on change', - (WidgetTester tester) async { - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final confirmationPasswordTextFieldFinder = find.byKey( - const Key('SignUpConfirmationPasswordTextField'), - ); - await tester.enterText( - confirmationPasswordTextFieldFinder, - 'a123456789P.', - ); - verify( - () => mockSignUpBloc.add( - const SignUpConfirmationPasswordChanged( - password: 'a123456789P.', - ), - ), - ).called(1); - }); - - testWidgets('[ConfirmationPasswordTextField] should display error state', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - password: const Password.dirty('password1234'), - confirmationPassword: 'password', - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final confirmationPasswordTextFieldFinder = find.byKey( - const Key('SignUpConfirmationPasswordTextField'), - ); - - expect( - tester - .widget(confirmationPasswordTextFieldFinder) - .decoration - ?.errorText, - isNotNull, - ); - }); - - testWidgets('[ConfirmationPasswordTextField] changes visibility', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - obscurePasswords: false, - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final confirmationPasswordTextFieldFinder = find.byKey( - const Key('SignUpConfirmationPasswordTextField'), - ); - - await tester.tap( - find.descendant( - of: confirmationPasswordTextFieldFinder, - matching: find.byType(IconButton), - ), - ); - - verify( - () => mockSignUpBloc.add( - const SignUpPasswordVisibilityChanged(obscure: true), - ), - ).called(1); - }); - - testWidgets('[PasswordTextField] changes visibility', - (WidgetTester tester) async { - when(() => mockSignUpBloc.state).thenReturn( - const SignUpState.initial().copyWith( - obscurePasswords: false, - ), - ); - - await tester.pumpApp( - const SignUpView(), - signUpBloc: mockSignUpBloc, - ); - - final passwordTextFieldFinder = find.byKey( - const Key('SignUpPasswordTextField'), - ); - - await tester.tap( - find.descendant( - of: passwordTextFieldFinder, - matching: find.byType(IconButton), - ), - ); - - verify( - () => mockSignUpBloc.add( - const SignUpPasswordVisibilityChanged(obscure: true), - ), - ).called(1); - }); - }); -} diff --git a/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart deleted file mode 100644 index d8d2a5c..0000000 --- a/todo/test/down_for_maintenance/down_for_maintenance_page_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/down_for_maintenance/down_for_maintenance.dart'; - -import '../helpers/helpers.dart'; - -void main() { - group('DownForMaintenancePage', () { - test('is a page', () { - expect(const DownForMaintenancePage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = - const DownForMaintenancePage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [DownForMaintenanceView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - const DownForMaintenancePage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(DownForMaintenanceView), findsOneWidget); - }); -} diff --git a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart b/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart deleted file mode 100644 index dc4d880..0000000 --- a/todo/test/down_for_maintenance/down_for_maintenance_view_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/down_for_maintenance/down_for_maintenance_view.dart'; -import 'package:todo/l10n/l10n.dart'; - -void main() { - group('DownForMaintenanceView', () { - testWidgets('renders correct text', (WidgetTester tester) async { - await tester.pumpWidget( - const MaterialApp( - home: DownForMaintenanceView(), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - ), - ); - - expect(find.byType(Text), findsOneWidget); - }); - - testWidgets('showAsDialog displays dialog', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (BuildContext context) { - return ElevatedButton( - onPressed: () { - DownForMaintenanceView.showAsDialog(context); - }, - child: const Text('Show Dialog'), - ); - }, - ), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - ), - ); - - await tester.tap(find.text('Show Dialog')); - await tester.pumpAndSettle(); - - expect(find.byType(Dialog), findsOneWidget); - }); - }); -} diff --git a/todo/test/force_upgrade/force_upgrade_page_test.dart b/todo/test/force_upgrade/force_upgrade_page_test.dart deleted file mode 100644 index 90aba63..0000000 --- a/todo/test/force_upgrade/force_upgrade_page_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/force_upgrade/force_upgrade.dart'; - -import '../helpers/helpers.dart'; - -void main() { - group('ForceUpgradePage', () { - test('is a page', () { - expect(const ForceUpgradePage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = const ForceUpgradePage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [ForceUpgradeView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - const ForceUpgradePage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(ForceUpgradeView), findsOneWidget); - }); -} diff --git a/todo/test/force_upgrade/force_upgrade_view_test.dart b/todo/test/force_upgrade/force_upgrade_view_test.dart deleted file mode 100644 index 5465d08..0000000 --- a/todo/test/force_upgrade/force_upgrade_view_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/force_upgrade/force_upgrade.dart'; -import 'package:todo/l10n/l10n.dart'; - -void main() { - group('ForceUpgradeView test', () { - testWidgets('renders correct text', (WidgetTester tester) async { - await tester.pumpWidget( - const MaterialApp( - home: ForceUpgradeView(), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - ), - ); - - expect(find.byType(Text), findsOneWidget); - }); - - testWidgets('showAsDialog displays dialog', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (BuildContext context) { - return ElevatedButton( - onPressed: () { - ForceUpgradeView.showAsDialog(context); - }, - child: const Text('Show Dialog'), - ); - }, - ), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - ), - ); - - await tester.tap(find.text('Show Dialog')); - await tester.pumpAndSettle(); - - expect(find.byType(Dialog), findsOneWidget); - }); - - testWidgets('renders dialog with Ok button', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (BuildContext context) { - return ElevatedButton( - onPressed: () { - ForceUpgradeView.showAsDialog(context); - }, - child: const Text('Show Dialog'), - ); - }, - ), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - ), - ); - - await tester.tap(find.text('Show Dialog')); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Ok')); - await tester.pump(); - }); - }); -} diff --git a/todo/test/helpers/helpers.dart b/todo/test/helpers/helpers.dart deleted file mode 100644 index b15fe65..0000000 --- a/todo/test/helpers/helpers.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pump_app.dart'; diff --git a/todo/test/helpers/pump_app.dart b/todo/test/helpers/pump_app.dart deleted file mode 100644 index b1a435b..0000000 --- a/todo/test/helpers/pump_app.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:api_client/api_client.dart'; -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/app/app.dart'; -import 'package:todo/authentication/delete_account/delete_account.dart'; -import 'package:todo/authentication/forgot_password/forgot_password.dart'; -import 'package:todo/authentication/login/login.dart'; -import 'package:todo/authentication/sign_up/sign_up.dart'; -import 'package:todo/home/home.dart'; -import 'package:todo/l10n/l10n.dart'; -import 'package:todo/settings/bloc/settings_bloc.dart'; -import 'package:user_repository/user_repository.dart'; - -class MockAppBloc extends MockBloc implements AppBloc {} - -class MockLoginBloc extends MockBloc - implements LoginBloc {} - -class MockSignUpBloc extends MockBloc - implements SignUpBloc {} - -class MockDeleteAccountBloc - extends MockBloc - implements DeleteAccountBloc {} - -class MockForgotPasswordBloc - extends MockBloc - implements ForgotPasswordBloc {} - -class MockUserRepository extends Mock implements UserRepository {} - -class MockRouterConfig extends Mock implements GoRouter {} - -class MockBuildContext extends Mock implements BuildContext {} - -class MockNavigatorObserver extends Mock implements NavigatorObserver {} - -class MockApiClient extends Mock implements ApiClient {} - -class MockAppConfigResource extends Mock implements AppConfigResource {} - -class FakeRoute extends Fake implements Route {} - -class MockHomeBloc extends MockBloc implements HomeBloc {} - -class MockSettingsBloc extends MockBloc - implements SettingsBloc {} - -extension AppTester on WidgetTester { - Future pumpApp( - Widget widgetUnderTest, { - AppBloc? appBloc, - SignUpBloc? signUpBloc, - LoginBloc? loginBloc, - ForgotPasswordBloc? forgotPasswordBloc, - DeleteAccountBloc? deleteAccountBloc, - TargetPlatform? platform, - NavigatorObserver? navigatorObserver, - UserRepository? userRepository, - HomeBloc? homeBloc, - SettingsBloc? settingsBloc, - }) async { - final router = GoRouter( - observers: navigatorObserver == null ? [] : [navigatorObserver], - routes: [ - GoRoute( - path: '/', - builder: (context, state) => Scaffold( - body: Builder( - builder: (context) { - return widgetUnderTest; - }, - ), - ), - ), - GoRoute( - path: '/signUpTestPath', - builder: (context, state) => Scaffold( - body: Builder( - builder: (context) { - return const SignUpView(); - }, - ), - ), - ), - ], - ); - await pumpWidget( - MultiRepositoryProvider( - providers: [ - RepositoryProvider.value( - value: userRepository ?? MockUserRepository(), - ), - ], - child: MultiBlocProvider( - providers: [ - BlocProvider.value( - value: signUpBloc ?? MockSignUpBloc(), - ), - BlocProvider.value( - value: appBloc ?? MockAppBloc(), - ), - BlocProvider.value( - value: loginBloc ?? MockLoginBloc(), - ), - BlocProvider.value( - value: forgotPasswordBloc ?? MockForgotPasswordBloc(), - ), - BlocProvider.value( - value: homeBloc ?? MockHomeBloc(), - ), - BlocProvider.value( - value: settingsBloc ?? MockSettingsBloc(), - ), - BlocProvider.value( - value: deleteAccountBloc ?? MockDeleteAccountBloc(), - ), - ], - child: MaterialApp.router( - title: 'Todo', - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - routerConfig: router, - ), - ), - ), - ); - await pump(); - } -} diff --git a/todo/test/home/bloc/home_bloc_test.dart b/todo/test/home/bloc/home_bloc_test.dart deleted file mode 100644 index 54a662b..0000000 --- a/todo/test/home/bloc/home_bloc_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/home/home.dart'; - -void main() { - group('HomeBloc', () { - blocTest( - 'emits [] when nothing is added', - build: HomeBloc.new, - expect: () => [], - ); - }); -} diff --git a/todo/test/home/bloc/home_event_test.dart b/todo/test/home/bloc/home_event_test.dart deleted file mode 100644 index b0288d5..0000000 --- a/todo/test/home/bloc/home_event_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/home/home.dart'; - -void main() { - test('supports value comparison', () { - expect(const HomeEvent().props, equals([])); - expect( - const HomeEvent(), - const HomeEvent(), - ); - }); -} diff --git a/todo/test/home/bloc/home_state_test.dart b/todo/test/home/bloc/home_state_test.dart deleted file mode 100644 index e52c4fa..0000000 --- a/todo/test/home/bloc/home_state_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/home/home.dart'; - -void main() { - test('supports value comparison', () { - expect( - const HomeState.initial().props, - equals([const HomeState.initial().status, 0]), - ); - expect( - const HomeState.initial(), - const HomeState.initial(), - ); - }); - - test('returns a copy with updated status', () { - expect( - const HomeState.initial().copyWith(status: HomeStatus.loading), - const HomeState(status: HomeStatus.loading), - ); - expect(const HomeState.initial().copyWith(), const HomeState.initial()); - }); -} diff --git a/todo/test/home/view/home_page_test.dart b/todo/test/home/view/home_page_test.dart deleted file mode 100644 index b6636be..0000000 --- a/todo/test/home/view/home_page_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/home/home.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - group('HomePage', () { - test('is a page', () { - expect(const HomePage(), isA>()); - }); - }); - - test('route is a [MaterialPageRoute]', () { - final route = const HomePage().createRoute(MockBuildContext()); - expect(route, isA>()); - }); - - testWidgets('route navigates to [HomeView]', (tester) async { - await tester.pumpApp( - Builder( - builder: (context) { - return SizedBox( - child: ElevatedButton( - child: const Text('Navigate'), - onPressed: () => Navigator.of(context).push( - const HomePage().createRoute(context), - ), - ), - ); - }, - ), - ); - await tester.tap(find.byType(ElevatedButton)); - await tester.pumpAndSettle(); - expect(find.byType(HomeView), findsOneWidget); - }); -} diff --git a/todo/test/home/view/home_view_test.dart b/todo/test/home/view/home_view_test.dart deleted file mode 100644 index 7201508..0000000 --- a/todo/test/home/view/home_view_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/home/home.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - late NavigatorObserver mockNavigatorObserver; - late HomeBloc mockHomeBloc; - - setUpAll(() { - registerFallbackValue(FakeRoute()); - mockHomeBloc = MockHomeBloc(); - when(() => mockHomeBloc.state).thenReturn(const HomeState.initial()); - }); - - setUp(() { - mockNavigatorObserver = MockNavigatorObserver(); - }); - group('HomeView', () { - testWidgets('renders correctly', (WidgetTester tester) async { - await tester.pumpApp( - const HomeView(), - homeBloc: mockHomeBloc, - ); - - expect( - find.byKey( - const Key('HomeAppBar'), - ), - findsOneWidget,); - expect(find.byType(Placeholder), findsOneWidget); - }); - - testWidgets('navigates to [DeleteAccountView] when delete button is tapped', - (WidgetTester tester) async { - await tester.pumpApp( - const HomeView(), - navigatorObserver: mockNavigatorObserver, - homeBloc: mockHomeBloc, - ); - - await tester.tap(find.byKey( - const Key('HomeDeleteAccountButton'), - ),); - await tester.pumpAndSettle(); - verify(() => mockNavigatorObserver.didPush(any(), any())); - }); - - testWidgets('navigates to bottombar [Logout]', (WidgetTester tester) async { - await tester.pumpApp( - const HomeView(), - navigatorObserver: mockNavigatorObserver, - homeBloc: mockHomeBloc, - ); - - await tester.tap(find.byKey(const Key('HomeLogoutButton'))); - await tester.pumpAndSettle(); - verify(() => mockNavigatorObserver.didPush(any(), any())); - }); - - testWidgets('dialog shows when button is pressed', - (WidgetTester tester) async { - await tester.pumpApp( - const HomeView(), - homeBloc: mockHomeBloc, - ); - final button = find.byKey( - const Key('HomeLogoutButton'), - ); - expect(button, findsOneWidget); - await tester.runAsync(() async { - final response = await tester.press(button); - await response.up(); - }); - await tester.pumpAndSettle(); - expect(find.byType(AlertDialog), findsOneWidget); - }); - - testWidgets('dialog dismisses when cancel button is pressed', - (WidgetTester tester) async { - await tester.pumpApp( - const HomeView(), - homeBloc: mockHomeBloc, - ); - final button = find.byKey( - const Key('HomeLogoutButton'), - ); - expect(button, findsOneWidget); - await tester.runAsync(() async { - final response = await tester.press(button); - await response.up(); - }); - await tester.pumpAndSettle(); - expect(find.byType(AlertDialog), findsOneWidget); - final cancelButton = find.byKey( - const Key('CancelButton'), - ); - await tester.tap(cancelButton); - await tester.pumpAndSettle(); - expect(find.byType(AlertDialog), findsNothing); - }); - }); -} diff --git a/todo/test/settings/bloc/settings_bloc_test.dart b/todo/test/settings/bloc/settings_bloc_test.dart deleted file mode 100644 index 3db95e5..0000000 --- a/todo/test/settings/bloc/settings_bloc_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/settings/settings.dart'; - -void main() { - late SettingsBloc settingsBloc; - - setUp(() { - settingsBloc = SettingsBloc(); - }); - - tearDown(() { - settingsBloc.close(); - }); - - group('TermsAndConditionsRetrieved event', () { - blocTest( - 'emits error state when canLaunchUrl returns false', - build: SettingsBloc.new, - act: (bloc) => bloc.add(const TermsAndConditionsRetrieved()), - expect: () => [ - const SettingsState.initial().copyWith(status: SettingsStatus.error), - ], - ); - }); -} diff --git a/todo/test/settings/bloc/settings_event_test.dart b/todo/test/settings/bloc/settings_event_test.dart deleted file mode 100644 index e0e0748..0000000 --- a/todo/test/settings/bloc/settings_event_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/settings/settings.dart'; - -void main() { - group('VersionNumberRetrieved', () { - test('props should be empty', () { - expect(const VersionNumberRetrieved().props, []); - }); - }); - - group('TermsAndConditionsRetrieved', () { - test('props should be empty', () { - expect(const TermsAndConditionsRetrieved().props, []); - }); - }); - - group('AboutUsRetrieved', () { - test('props should be empty', () { - expect(const AboutUsRetrieved().props, []); - }); - }); -} diff --git a/todo/test/settings/bloc/settings_state_test.dart b/todo/test/settings/bloc/settings_state_test.dart deleted file mode 100644 index e054e2c..0000000 --- a/todo/test/settings/bloc/settings_state_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:todo/settings/bloc/settings_bloc.dart'; - -void main() { - test('supports value comparison', () { - expect( - const SettingsState.initial().props, - equals([const SettingsState.initial().status, '']), - ); - expect( - const SettingsState.initial(), - const SettingsState.initial(), - ); - }); - - test('returns a copy with updated status', () { - expect( - const SettingsState.initial().copyWith( - status: SettingsStatus.loading, - appVersion: '1.0.0+1', - ), - const SettingsState( - status: SettingsStatus.loading, - appVersion: '1.0.0+1', - ), - ); - expect( - const SettingsState.initial().copyWith(), - const SettingsState.initial(), - ); - }); -} diff --git a/todo/test/settings/view/settings_view_test.dart b/todo/test/settings/view/settings_view_test.dart deleted file mode 100644 index 1b6d65f..0000000 --- a/todo/test/settings/view/settings_view_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:todo/settings/settings.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - late NavigatorObserver mockNavigatorObserver; - late SettingsBloc mockSettingsBloc; - - setUpAll(() { - registerFallbackValue(FakeRoute()); - mockSettingsBloc = MockSettingsBloc(); - when(() => mockSettingsBloc.state) - .thenReturn(const SettingsState.initial()); - }); - - setUp(() { - mockNavigatorObserver = MockNavigatorObserver(); - }); - - group('SettingsView', () { - testWidgets('renders SettingsView', (tester) async { - await tester.pumpApp( - const SettingsView(), - settingsBloc: mockSettingsBloc, - ); - - expect( - find.byKey( - const Key('SettingsAppBar'), - ), - findsOneWidget, - ); - }); - - testWidgets('renders ContactUsSettingsItem', (tester) async { - await tester.pumpApp( - const SettingsView(), - settingsBloc: mockSettingsBloc, - ); - - expect( - find.byKey( - const Key('ContactUsSettingsItem'), - ), - findsOneWidget, - ); - }); - - testWidgets('renders TermsAndConditionsSettingsItem', (tester) async { - await tester.pumpApp( - const SettingsView(), - settingsBloc: mockSettingsBloc, - ); - - expect( - find.byKey( - const Key('TermsAndConditionsSettingsItem'), - ), - findsOneWidget, - ); - }); - - testWidgets('renders AboutUsSettingsItem', (tester) async { - await tester.pumpApp( - const SettingsView(), - settingsBloc: mockSettingsBloc, - ); - - expect( - find.byKey( - const Key('AboutUsSettingsItem'), - ), - findsOneWidget, - ); - }); - - testWidgets('renders AppVersionSettingsItem', (tester) async { - await tester.pumpApp( - const SettingsView(), - settingsBloc: mockSettingsBloc, - ); - - expect( - find.byKey( - const Key('AppVersionSettingsItem'), - ), - findsOneWidget, - ); - }); - - testWidgets('navigates to DeleteAccountPage when delete account is tapped', - (tester) async { - await tester.pumpApp( - const SettingsView(), - navigatorObserver: mockNavigatorObserver, - settingsBloc: mockSettingsBloc, - ); - - await tester.tap(find.byKey(const Key('DeleteAccountSettingsItem'))); - await tester.pumpAndSettle(); - verify(() => mockNavigatorObserver.didPush(any(), any())); - }); - }); -} diff --git a/todo/web/favicon.png b/todo/web/favicon.png deleted file mode 100644 index 8aaa46ac1ae21512746f852a42ba87e4165dfdd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM diff --git a/todo/web/icons/Icon-192.png b/todo/web/icons/Icon-192.png deleted file mode 100644 index b749bfef07473333cf1dd31e9eed89862a5d52aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5292 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 diff --git a/todo/web/icons/Icon-512.png b/todo/web/icons/Icon-512.png deleted file mode 100644 index 88cfd48dff1169879ba46840804b412fe02fefd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8252 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 diff --git a/todo/web/icons/Icon-maskable-192.png b/todo/web/icons/Icon-maskable-192.png deleted file mode 100644 index eb9b4d76e525556d5d89141648c724331630325d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! diff --git a/todo/web/icons/Icon-maskable-512.png b/todo/web/icons/Icon-maskable-512.png deleted file mode 100644 index d69c56691fbdb0b7efa65097c7cc1edac12a6d3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20998 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 MPgg&ebxsLQ025IeI{*Lx diff --git a/todo/web/index.html b/todo/web/index.html deleted file mode 100644 index 3e82bab..0000000 --- a/todo/web/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Todo - - - - - - - - diff --git a/todo/web/manifest.json b/todo/web/manifest.json deleted file mode 100644 index 0de9f5e..0000000 --- a/todo/web/manifest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "Todo", - "short_name": "Todo", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A Somnio App", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} diff --git a/ui_package/__brick__/{{package_name.snakeCase()}}_ui/gallery/pubspec.yaml b/ui_package/__brick__/{{package_name.snakeCase()}}_ui/gallery/pubspec.yaml index a2a6c9e..34bbb3c 100644 --- a/ui_package/__brick__/{{package_name.snakeCase()}}_ui/gallery/pubspec.yaml +++ b/ui_package/__brick__/{{package_name.snakeCase()}}_ui/gallery/pubspec.yaml @@ -11,7 +11,6 @@ dependencies: path: ../ flutter: sdk: flutter - flutter_asset_generator: ^1.0.2 dev_dependencies: flutter_lints: ^2.0.1 diff --git a/ui_package/__brick__/{{package_name.snakeCase()}}_ui/pubspec.yaml b/ui_package/__brick__/{{package_name.snakeCase()}}_ui/pubspec.yaml index 6f1278f..0904c4d 100644 --- a/ui_package/__brick__/{{package_name.snakeCase()}}_ui/pubspec.yaml +++ b/ui_package/__brick__/{{package_name.snakeCase()}}_ui/pubspec.yaml @@ -10,12 +10,11 @@ environment: dependencies: flutter: sdk: flutter - flutter_asset_generator: ^1.0.2 flutter_svg: ^2.0.10+1 dev_dependencies: - build_runner: ^2.4.4' - flutter_gen_runner: ^5.3.1 + build_runner: ^2.10.4 + flutter_gen_runner: ^5.12.0 flutter_test: sdk: flutter very_good_analysis: ^5.1.0