diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e449499..ef510fd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,27 @@ -# Pull Request (PR) Checklist + -- [ ] PR title includes Jira ticket reference -- if no ticket available create one if this PR is a fix or feat -- [ ] Branch name includes Jira ticket reference (parent ticket and sub-task if available), e.g. 'feat/APP-XXX_Some_Feature' or feat/APP-XXX_APP-XXX_Some_Sub_Task or 'fix/APP-XXX_Some_Fix' or 'refactor/Some_Refactoring' -- [ ] All PR checks pass and changes under /domain are covered with unit tests -- [ ] Jira ticket provides testing infos for QA -- add a comment if needed e.q. for pre-requisites, order of API calls, ... -- [ ] When merging this PR the commit message follows [conventional-commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), e.g. 'feat(some optional epic): add some feature (APP-XXX) (#XXX)' +### General -## Related to other PRs +- [Jira Ticket](#) -## Notes for PR reviewers + + +### Checklists + +- [ ] Platform specific changes (input, image picking etc) are tested on both of the platforms (Android and iOS) +- [ ] It is tested that the UI changes are rendered correctly on different device sizes (such as long lists or expanded rows) with safe area conditions. +- [ ] Edge cases, such as responses being empty or invalid, missing data, no internet connection etc, are tested and the app works as expected. + +### Showcase + +- [ ] Either showcase screenshots / videos are attached, or this PR does not require such any showcase. + + diff --git a/.github/workflows/pull_request_validation.yml b/.github/workflows/pull_request_validation.yml index bca1805..46454a5 100644 --- a/.github/workflows/pull_request_validation.yml +++ b/.github/workflows/pull_request_validation.yml @@ -1,19 +1,48 @@ -# This workflow builds, analyses and tests every pull request. +# This workflow builds, analyzes, and tests every pull request. name: Pull Request Validation -on: pull_request +on: + push: + branches: + - develop + - main + - master + + pull_request: + jobs: analyze-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 + - name: 📚 Git Checkout + uses: actions/checkout@v4 + + - name: ♨️ Setup Java + uses: actions/setup-java@v3 with: - java-version: "12.x" - - uses: subosito/flutter-action@v1 + java-version: '21' + distribution: 'temurin' + + - name: 🐦 Setup Flutter + uses: subosito/flutter-action@v2 with: - channel: "stable" - - run: flutter pub get - - run: flutter pub run build_runner build --delete-conflicting-outputs - - run: flutter build apk --release --flavor production -t lib/main_production.dart - - run: flutter analyze - - run: flutter test + channel: 'stable' + cache: true + cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} + + - name: 🛎️ Checking Flutter Version + run: flutter --version + + - name: 📦 Install Dependencies + run: flutter pub get + + - name: 🛠️ Build Runner (Generate Code) + run: flutter pub run build_runner build --delete-conflicting-outputs + + - name: 🕵️ Analyze Code + run: flutter analyze lib test + + - name: 🏗️ Build APK (Production Flavor) + run: flutter build apk --release --flavor production -t lib/main_production.dart + + - name: 🧪 Run Tests + run: flutter test diff --git a/.gitignore b/.gitignore index 91264a1..51c4b51 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ .history .svn/ +# Local Mason Files +.mason/ +mason-lock.json + # IntelliJ related *.iml *.ipr @@ -43,10 +47,13 @@ app.*.map.json # Flutter code generation *.freezed.dart *.g.dart -nstack.dart *.gr.dart *.config.dart lib/gen/** # Dependencies -**.lock +# Pubspec.lock should be checked out to git +# to ensure everyone in the team is using the same version. +# Recommended by Flutter as well. +# We can git ignore Podfile.lock only. +Podfile.lock diff --git a/.mason/bricks.json b/.mason/bricks.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.mason/bricks.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/.metadata b/.metadata index cd984dd..c689480 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,27 @@ # This file should be version controlled and should not be manually edited. version: - revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 - channel: stable + revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: android + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + + # 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/.run/Development Debug.run.xml b/.run/Development Debug.run.xml new file mode 100644 index 0000000..dab6922 --- /dev/null +++ b/.run/Development Debug.run.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.run/staging_debug.run.xml b/.run/Staging Debug.run.xml similarity index 62% rename from .run/staging_debug.run.xml rename to .run/Staging Debug.run.xml index deb8136..d1e05cf 100644 --- a/.run/staging_debug.run.xml +++ b/.run/Staging Debug.run.xml @@ -1,5 +1,7 @@ + diff --git a/.run/production_debug.run.xml b/.run/production_debug.run.xml deleted file mode 100644 index 8cce292..0000000 --- a/.run/production_debug.run.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.run/production_release.run.xml b/.run/production_release.run.xml deleted file mode 100644 index 1181e15..0000000 --- a/.run/production_release.run.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.run/staging_release.run.xml b/.run/staging_release.run.xml deleted file mode 100644 index 8025574..0000000 --- a/.run/staging_release.run.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..5fa9c14 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + "recommendations": [ + "dart-code.dart-code", + "dart-code.flutter", + "usernamehw.errorlens", + "felixangelov.mason", + "felixangelov.bloc", + "nivisi.dart-build-runner-tools", + "tenraneko.pubspec-dependency-opener", + "jeroen-meijer.pubspec-assist", + "davidanson.vscode-markdownlint" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index eb630bf..b21280b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,38 +1,32 @@ { - // 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": "Development", - "request": "launch", - "type": "dart", - "program": "lib/main_development.dart", - "args": [ - "--flavor", - "development" - ] - }, - { - "name": "Staging", - "request": "launch", - "type": "dart", - "program": "lib/main_staging.dart", - "args": [ - "--flavor", - "staging" - ] - }, - { - "name": "Production", - "request": "launch", - "type": "dart", - "program": "lib/main_production.dart", - "args": [ - "--flavor", - "production" - ] - }, - ] + // 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": "Development", + "request": "launch", + "type": "dart", + "program": "lib/main_development.dart", + "args": [ + "--flavor", + "development", + "--dart-define-from-file", + "lib/config/dart_define_keys/dart_define_development_keys.json" + ] + }, + { + "name": "Staging", + "request": "launch", + "type": "dart", + "program": "lib/main_staging.dart", + "args": [ + "--flavor", + "staging", + "--dart-define-from-file", + "lib/config/dart_define_keys/dart_define_staging_keys.json" + ] + } + ] } diff --git a/.vscode/ml-flutter.code-snippets b/.vscode/ml-flutter.code-snippets index dd4ad69..056f778 100644 --- a/.vscode/ml-flutter.code-snippets +++ b/.vscode/ml-flutter.code-snippets @@ -1,22 +1,63 @@ { - "Freezed Class": { - "prefix": "freezed", - "body": [ - "import 'package:freezed_annotation/freezed_annotation.dart';", - "", - "part '${1:file}.freezed.dart';", - "part '${1:file}.g.dart';", - "", - "@freezed", - "class ${2:ClassName} with _$${2:ClassName} {", - " const factory ${2:ClassName}({", - " required ${0:param},", - " }) = _${2:ClassName};", - "", - " factory ${2:ClassName}.fromJson(Map json) =>", - " _$${2:ClassName}FromJson(json);", - "}" - ], - "description": "Creates basement for a freezed class." - }, -} \ No newline at end of file + "Remote Data Source": { + "prefix": "remote_data_source", + "description": "Snippet for quick creation of a remote data source.", + "body": [ + "import 'package:dio/dio.dart';", + "import 'package:flutter_template/injection/network_module.dart';", + "import 'package:injectable/injectable.dart';", + "import 'package:retrofit/retrofit.dart';", + "", + "part '$TM_FILENAME_BASE.g.dart';", + "", + "@RestApi()", + "@lazySingleton", + "abstract class ${2:Scope}RemoteDataSource {", + " @factoryMethod", + " factory $2RemoteDataSource(", + " @Named(dioAuthenticated) Dio dio,", + " ) = _$2RemoteDataSource;", + "", + " ${3:/* Fill your calls here */}", + "}", + "" + ] + }, + "Freezed Class": { + "prefix": "freezed", + "body": [ + "import 'package:freezed_annotation/freezed_annotation.dart';", + "", + "part '${1:file}.freezed.dart';", + "part '${1:file}.g.dart';", + "", + "@freezed", + "class ${2:ClassName} with _$${2:ClassName} {", + " const factory ${2:ClassName}({", + " required ${0:param},", + " }) = _${2:ClassName};", + "", + " factory ${2:ClassName}.fromJson(Map json) =>", + " _$${2:ClassName}FromJson(json);", + "}" + ], + "description": "Creates basement for a freezed class." + }, + "Remapper": { + "prefix": "remapper", + "description": "Create a basement for a remapper class.", + "body": [ + "import 'package:injectable/injectable.dart';", + "", + "@lazySingleton", + "class ${1:Entity}Remapper {", + " ${1:Entity}Entity fromResponse(${1:Entity}Response response) {", + " return ${1:Entity}Entity(", + " ${2}", + " );", + " }", + "", + "}" + ] + } +} diff --git a/README.md b/README.md index 50a98b3..bebd50a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,47 @@ # App name - Flutter ## 📖 Project description + This project serves as a template project for Flutter applications. You can generate a new repository by clicking "Use this template" above. ## 🔧 Setup and Installation -- Execute `$ flutter doctor` to verify installations -- Execute `$ pub get` to pull project dependencies -- Execute a run configuration of choice - - `$ flutter run --flavor staging -t lib/main_staging.dart` - - `$ flutter run --flavor production -t lib/main_production.dart` + +1. Execute `$ flutter doctor` to verify installations. +2. Execute `$ pub get` to pull project dependencies. +3. Execute a run configuration of your choice: + + ```sh + flutter run --flavor development -t lib/main_development.dart --dart-define-from-file=lib/config/dart_define_keys/dart_define_development_keys.json + ``` + + ```sh + flutter run --flavor staging -t lib/main_staging.dart --dart-define-from-file=lib/config/dart_define_keys/dart_define_staging_keys.json` + ``` + +> [!IMPORTANT] +> ⚠️ **Running the Production Flavor** +> +> It is **not recommended** to run the production flavor directly from the IDE +> (VS Code or Android Studio) as it may be run by mistake. For ease and +> accuracy, +> it is advised to run flavors via the **VS Code launch menu** or the +> **Android Studio Run menu** instead. +> +> To use the build runner, use this command: +> +> ```sh +> dart run build_runner build --delete-conflicting-outputs +> ``` ## 🌲 Branches -* `master` - Latest version in the app store. -* `develop` - Default. Feature branches are merged in when complete and then deleted. + +- `main` - Latest version in the app store. +- `develop` - Default. Feature branches are merged in when complete and then deleted. ## 🔗 Useful links + - [Jira Board](https://TODO) - [Confluence](https://TODO) - [Slack Channel](https://TODO) - [OpenAPI Specification](https://TODO) -- [NStack](https://TODO) - [Figma Design](https://TODO) -- [UI Manifesto](https://github.com/monstar-lab-oss/ui-manifesto) diff --git a/analysis_options.yaml b/analysis_options.yaml index 149ffc4..be9c419 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,78 +5,14 @@ # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - rules: - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_print - - avoid_relative_lib_imports - - avoid_return_types_on_setters - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - avoid_void_async - - await_only_futures - - camel_case_extensions - - curly_braces_in_flow_control_structures - - empty_catches - - empty_constructor_bodies - - library_names - - library_prefixes - - no_duplicate_case_values - - null_closures - - omit_local_variable_types - - prefer_adjacent_string_concatenation - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_contains - - prefer_final_fields - - prefer_for_elements_to_map_fromIterable - - prefer_generic_function_type_aliases - - prefer_if_null_operators - - prefer_inlined_adds - - prefer_is_empty - - prefer_is_not_empty - - prefer_iterable_whereType - - prefer_single_quotes - - prefer_spread_collections - - recursive_getters - - slash_for_doc_comments - - sort_child_properties_last - - type_init_formals - - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_new - - unnecessary_null_in_if_null_operators - - unnecessary_this - - unrelated_type_equality_checks - - unsafe_html - - use_colored_box - - use_decorated_box - - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - - use_rethrow_when_possible - - valid_regexps +include: package:monstarlab_lints/analysis_options.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options - analyzer: errors: - # Ignore invalid annotations since freezed annotation and json serializable required it + # Ignore invalid annotations since freezed annotation and json serializable required it invalid_annotation_target: ignore exclude: # ignore DI @@ -86,7 +22,5 @@ analyzer: - "**/*.g.dart" # Ignore warnings in files generated by Freezed specifically. - "**/*.freezed.dart" - # Ignore warnings in files generated by NStack specifically. - - "**/*nstack.dart" # Ignore bricks - bricks/** diff --git a/android/.gitignore b/android/.gitignore index 6f56801..55afd91 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # 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/android/app/build.gradle b/android/app/build.gradle index db2aabc..4209514 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,96 +1,82 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } +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" } +// TODO: Change the appName, it will be used as app label +def appName = "Monstarlab Flutter Template" -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.") -} +// TODO: Change the applicationId for production +// On staging it will add .staging and on development it will add .development as suffix. +def packageName = "com.monstarlab.flutter" -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' +ext { + androidMinSdkVersion = 21 + androidCompileAndTargetSdkVersion = 34 } -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 + namespace = "$packageName" + compileSdk = androidCompileAndTargetSdkVersion + ndkVersion = "27.0.12077973" compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.flutter_template" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "$packageName" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = androidMinSdkVersion + targetSdk = androidCompileAndTargetSdkVersion + 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.debug + signingConfig = signingConfigs.debug } } - //TODO: Change resValue application name - flavorDimensions "flavor-type" + flavorDimensions.add("flavor-type") productFlavors { - development { - dimension "flavor-type" - applicationIdSuffix ".development" - versionNameSuffix "-development" - resValue "string", "app_name", "Application Name Development" - signingConfig signingConfigs.debug + create("development") { + dimension = "flavor-type" + applicationIdSuffix = ".development" + versionNameSuffix = "-development" + manifestPlaceholders["appName"] = "[DEV] $appName" + signingConfig = signingConfigs.getByName("debug") } - staging { - dimension "flavor-type" - applicationIdSuffix ".staging" - versionNameSuffix "-staging" - resValue "string", "app_name", "Application Name Staging" - signingConfig signingConfigs.debug + create("staging") { + dimension = "flavor-type" + applicationIdSuffix = ".staging" + versionNameSuffix = "-staging" + manifestPlaceholders["appName"] = "[STG] $appName" + signingConfig = signingConfigs.getByName("debug") } - production { - dimension "flavor-type" - resValue "string", "app_name", "Application Name" - // TODO: After adding your own signing config for the release build. - // Change this to signingConfigs.release - signingConfig signingConfigs.debug + create("production") { + dimension = "flavor-type" + manifestPlaceholders["appName"] = "$appName" + // TODO: After adding your own signing config for the release build, + // Change this to + // signingConfig = signingConfigs.getByName("release") + signingConfig = signingConfigs.getByName("debug") } - } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..a2ef6bc --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,6 @@ +-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue +-dontwarn com.google.errorprone.annotations.CheckReturnValue +-dontwarn com.google.errorprone.annotations.Immutable +-dontwarn com.google.errorprone.annotations.RestrictedApi +-dontwarn javax.annotation.Nullable +-dontwarn javax.annotation.concurrent.GuardedBy diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 400255c..399f698 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ee3b43f..aaee568 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,27 +1,29 @@ - - + + + android:label="${appName}" + android:name="${applicationName}" + android:icon="@mipmap/ic_launcher"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> - - + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt deleted file mode 100644 index 8d61f3e..0000000 --- a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.flutter_template - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/android/app/src/main/kotlin/com/monstarlab/flutter/MainActivity.kt b/android/app/src/main/kotlin/com/monstarlab/flutter/MainActivity.kt new file mode 100644 index 0000000..3c5f278 --- /dev/null +++ b/android/app/src/main/kotlin/com/monstarlab/flutter/MainActivity.kt @@ -0,0 +1,5 @@ +package com.monstarlab.flutter + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 3db14bb..06952be 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -3,7 +3,7 @@ diff --git a/android/build.gradle b/android/build.gradle index 166e389..d2ffbff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.20' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a..2597170 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 595fb86..afa1e8e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/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.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..da301b7 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +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 + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version '8.7.2' apply false + id "org.jetbrains.kotlin.android" version "2.0.20" apply false +} + +include ":app" diff --git a/assets/animations/audio_wave.json b/assets/animations/audio_wave.json new file mode 100644 index 0000000..c7ee5c7 --- /dev/null +++ b/assets/animations/audio_wave.json @@ -0,0 +1 @@ +{"nm":"コンポ 1","ddd":0,"h":600,"w":600,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"シェイプレイヤー 5","sr":1,"st":0,"op":150.000006109625,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"シェイプ 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[226,-236],[226,242]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"パスのトリミング 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[80],"t":0},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[70],"t":5},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[90],"t":11},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[60],"t":17},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[85],"t":25},{"s":[80],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[20],"t":0},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[30],"t":5},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[10],"t":11},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[40],"t":17},{"o":{"x":0.48,"y":0.04},"i":{"x":0.52,"y":0.96},"s":[15],"t":25},{"s":[20],"t":30.0000012219251}],"ix":1},"m":2},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"線 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[0.2784,0.5922,1],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"塗り 1","c":{"a":0,"k":[1,0,0],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"シェイプレイヤー 4","sr":1,"st":0,"op":150.000006109625,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"シェイプ 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114,-236],[114,242]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"パスのトリミング 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[60],"t":0},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[80],"t":7},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[60],"t":14},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":22},{"s":[60],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[40],"t":0},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[20],"t":7},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[40],"t":14},{"o":{"x":0.42,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":22},{"s":[40],"t":30.0000012219251}],"ix":1},"m":2},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"線 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[0.2784,0.5922,1],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"塗り 1","c":{"a":0,"k":[1,0,0],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"シェイプレイヤー 3","sr":1,"st":0,"op":150.000006109625,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"シェイプ 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-236],[-2,242]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"パスのトリミング 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[90],"t":0},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[65],"t":8},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[75],"t":15},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[60],"t":21},{"s":[90],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[10],"t":0},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[35],"t":8},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[25],"t":15},{"o":{"x":0.84,"y":0},"i":{"x":0.16,"y":1},"s":[40],"t":21},{"s":[10],"t":30.0000012219251}],"ix":1},"m":2},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"線 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[0.2784,0.5922,1],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"塗り 1","c":{"a":0,"k":[1,0,0],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"シェイプレイヤー 2","sr":1,"st":0,"op":150.000006109625,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"シェイプ 1","ix":1,"cix":2,"np":5,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-114,-236],[-114,242]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"パスのトリミング 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[60],"t":0},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[85],"t":8},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[70],"t":15},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[90],"t":22},{"s":[60],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[40],"t":0},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[15],"t":8},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[30],"t":15},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[10],"t":22},{"s":[40],"t":30.0000012219251}],"ix":1},"m":2},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"線 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[0.2784,0.5922,1],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"塗り 1","c":{"a":0,"k":[1,0,0],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 2","ix":5,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114,-236],[114,242]]},"ix":2}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4},{"ty":4,"nm":"シェイプレイヤー 1","sr":1,"st":0,"op":150.000006109625,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[300,300,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"シェイプ 1","ix":1,"cix":2,"np":5,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-226,-236],[-226,242]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"パスのトリミング 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[80],"t":0},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[60],"t":8},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[90],"t":15},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[65],"t":22},{"s":[80],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[20],"t":0},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[40],"t":8},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[10],"t":15},{"o":{"x":0.4,"y":0.8},"i":{"x":0.74,"y":1},"s":[35],"t":22},{"s":[20],"t":30.0000012219251}],"ix":1},"m":2},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"線 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[0.2784,0.5922,1],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"塗り 1","c":{"a":0,"k":[1,0,0],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"パス 2","ix":5,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[226,-236],[226,242]]},"ix":2}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5}],"v":"5.2.1","fr":30,"op":30,"ip":0,"assets":[]} \ No newline at end of file diff --git a/bricks/README.md b/bricks/README.md deleted file mode 100644 index 44e2ef1..0000000 --- a/bricks/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Monstar Bricks 🧱 - -A collection of bricks. - -## Installation - -Ensure you have the [mason_cli](https://github.com/felangel/mason/tree/master/packages/mason_cli) installed. Bricks can be installed from [brickhub.dev](https://brickhub.dev). - -> brickhub is currently in closed alpha testing. - -```sh -mason add [--path ] -``` - -## Bricks - -| Brick | Description | -| ----------------------- | ------------------------------------ | -| `bloc` | Generate a new Bloc | -| `cubit` | Generate a new Cubit | -| `usecase` | Generate a new usecase | -| `output_usecase` | Generate a new output usecase | -| `stream_usecase` | Generate a new stream usecase | -| `stream_output_usecase` | Generate a new stream output usecase | -| `future_usecase` | Generate a new future usecase | -| `future_output_usecase` | Generate a new future output usecase | diff --git a/bricks/future_output_usecase/CHANGELOG.md b/bricks/future_output_usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/future_output_usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/future_output_usecase/LICENSE b/bricks/future_output_usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/future_output_usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/future_output_usecase/README.md b/bricks/future_output_usecase/README.md deleted file mode 100644 index c4a1cc4..0000000 --- a/bricks/future_output_usecase/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# usecase - -Generate a new FutureOutputUseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make future_output_usecase --name GetStories --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/future_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/future_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index 305c007..0000000 --- a/bricks/future_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends FutureOutputUseCase<{{output_type.pascalCase()}}> { - @override - Future<{{output_type.pascalCase()}}> run() { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/future_output_usecase/brick.yaml b/bricks/future_output_usecase/brick.yaml deleted file mode 100644 index 1d7bd6e..0000000 --- a/bricks/future_output_usecase/brick.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: future_output_usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? diff --git a/bricks/future_usecase/CHANGELOG.md b/bricks/future_usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/future_usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/future_usecase/LICENSE b/bricks/future_usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/future_usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/future_usecase/README.md b/bricks/future_usecase/README.md deleted file mode 100644 index 1f2df34..0000000 --- a/bricks/future_usecase/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# usecase - -Generate a new FutureUseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make future_usecase --name GetStories --input_type Query --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `input_type` | The input type of the usecase | `String` | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/future_usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/future_usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index 3f75894..0000000 --- a/bricks/future_usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends FutureUseCase<{{input_type.pascalCase()}}, {{output_type.pascalCase()}}> { - @override - Future<{{output_type.pascalCase()}}> run({{input_type.pascalCase()}} input) { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/future_usecase/brick.yaml b/bricks/future_usecase/brick.yaml deleted file mode 100644 index f1dbb13..0000000 --- a/bricks/future_usecase/brick.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: future_usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - input_type: - type: string - description: The use case input type (example String) - default: String - prompt: What is the input type for the use case? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? - diff --git a/bricks/output_usecase/CHANGELOG.md b/bricks/output_usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/output_usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/output_usecase/LICENSE b/bricks/output_usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/output_usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/output_usecase/README.md b/bricks/output_usecase/README.md deleted file mode 100644 index 798d7fc..0000000 --- a/bricks/output_usecase/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# usecase - -Generate a new OutputUseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make output_usecase --name GetStories --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index 22f148a..0000000 --- a/bricks/output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends OutputUseCase<{{output_type.pascalCase()}}> { - @override - {{output_type.pascalCase()}} run() { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/output_usecase/brick.yaml b/bricks/output_usecase/brick.yaml deleted file mode 100644 index 3a88536..0000000 --- a/bricks/output_usecase/brick.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: output_usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? diff --git a/bricks/stream_output_usecase/CHANGELOG.md b/bricks/stream_output_usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/stream_output_usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/stream_output_usecase/LICENSE b/bricks/stream_output_usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/stream_output_usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/stream_output_usecase/README.md b/bricks/stream_output_usecase/README.md deleted file mode 100644 index 7db6bf0..0000000 --- a/bricks/stream_output_usecase/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# usecase - -Generate a new StreamOutputUseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make stream_output_usecase --name GetStories --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/stream_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/stream_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index af655e1..0000000 --- a/bricks/stream_output_usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends StreamOutputUseCase<{{output_type.pascalCase()}}> { - @override - Stream<{{output_type.pascalCase()}}> run() { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/stream_output_usecase/brick.yaml b/bricks/stream_output_usecase/brick.yaml deleted file mode 100644 index 1984650..0000000 --- a/bricks/stream_output_usecase/brick.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: stream_output_usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? diff --git a/bricks/stream_usecase/CHANGELOG.md b/bricks/stream_usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/stream_usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/stream_usecase/LICENSE b/bricks/stream_usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/stream_usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/stream_usecase/README.md b/bricks/stream_usecase/README.md deleted file mode 100644 index e33947e..0000000 --- a/bricks/stream_usecase/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# usecase - -Generate a new StreamUseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make stream_usecase --name GetStories --input_type Query --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `input_type` | The input type of the usecase | `String` | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/stream_usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/stream_usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index 4e0a3d6..0000000 --- a/bricks/stream_usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends StreamUseCase<{{input_type.pascalCase()}}, {{output_type.pascalCase()}}> { - @override - Stream<{{output_type.pascalCase()}}> run({{input_type.pascalCase()}} input) { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/stream_usecase/brick.yaml b/bricks/stream_usecase/brick.yaml deleted file mode 100644 index a7e53cc..0000000 --- a/bricks/stream_usecase/brick.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: stream_usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - input_type: - type: string - description: The use case input type (example String) - default: String - prompt: What is the input type for the use case? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? diff --git a/bricks/usecase/CHANGELOG.md b/bricks/usecase/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bricks/usecase/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bricks/usecase/LICENSE b/bricks/usecase/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bricks/usecase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bricks/usecase/README.md b/bricks/usecase/README.md deleted file mode 100644 index 3153d52..0000000 --- a/bricks/usecase/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# usecase - -Generate a new UseCase in [Dart][1]. - -## Usage 🚀 - -```sh -mason make usecase --name GetStories --input_type Query --output_type List -``` - -## Variables ✨ - -| Variable | Description | Default | Type | -| ------------- | ------------------------------ | -------- | -------- | -| `name` | The name of the usecase class | None | `string` | -| `input_type` | The input type of the usecase | `String` | `string` | -| `output_type` | The output type of the usecase | `String` | `string` | - -## Output 📦 - -```sh -└── get_stories_usecase.dart -``` - -[1]: https://dart.dev diff --git a/bricks/usecase/__brick__/{{name.snakeCase()}}_usecase.dart b/bricks/usecase/__brick__/{{name.snakeCase()}}_usecase.dart deleted file mode 100644 index 88431a4..0000000 --- a/bricks/usecase/__brick__/{{name.snakeCase()}}_usecase.dart +++ /dev/null @@ -1,6 +0,0 @@ -class {{name.pascalCase()}}UseCase extends UseCase<{{input_type.pascalCase()}}, {{output_type.pascalCase()}}> { - @override - {{output_type.pascalCase()}} run({{input_type.pascalCase()}} input) { - // TODO: implement usecase - } -} \ No newline at end of file diff --git a/bricks/usecase/brick.yaml b/bricks/usecase/brick.yaml deleted file mode 100644 index dece5c1..0000000 --- a/bricks/usecase/brick.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: usecase -description: A new brick created with the Mason CLI. - -# The following defines the version and build number for your brick. -# A version number is three numbers separated by dots, like 1.2.34 -# followed by an optional build number (separated by a +). -version: 0.1.0+1 - -# The following defines the environment for the current brick. -# It includes the version of mason that the brick requires. -environment: - mason: ">=0.1.0-dev <0.1.0" - -# Variables specify dynamic values that your brick depends on. -# Zero or more variables can be specified for a given brick. -# Each variable has: -# * a type (string, number, or boolean) -# * an optional short description -# * an optional default value -# * an optional prompt phrase used when asking for the variable. -vars: - name: - type: string - description: Use case name - prompt: What is the use case name? - input_type: - type: string - description: The use case input type (example String) - default: String - prompt: What is the input type for the use case? - output_type: - type: string - description: The use case output type (example String) - default: String - prompt: What is the output type for the use case? diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..6db540b --- /dev/null +++ b/build.yaml @@ -0,0 +1,81 @@ +targets: + $default: + builders: + # Serializer + json_serializable: + options: + explicit_to_json: true + # By default, field_rename: none, More options — + # snake, kebab, pascal, etc. To use, uncomment this below line — + # field_rename: snake + generate_for: + include: + # data layer: + - lib/data/model/**.dart + - lib/data/services/**_request.dart + - lib/data/services/**_response.dart + - lib/data/response_objects/**.dart + + # domain layer: + # Note: We added domain layer? Why? Because sometimes, we may need + # some freezed classes to be saved on storage where toJson() is required. + # We should not allow the entire `/entities` folder here, as entities don't need fromJson(), toJson(), etc. + # Specifying exact file will help build runner to run fast. + - lib/domain/entities/user.dart + + # Retrofit Classes / Remote Data Sources + retrofit_generator: + generate_for: + include: + - lib/data/**/data_sources/remote/*_remote_data_source.dart + + # Data Classes, Cloning + freezed:freezed: + generate_for: + include: + # data layer: + - lib/data/model/**.dart + - lib/data/services/**_request.dart + - lib/data/services/**_response.dart + - lib/data/response_objects/**.dart + + # domain layer: + - lib/domain/common/**.dart + - lib/domain/entities/**.dart + + # presentation layer: + - lib/presentation/features/**_state.dart + + # Dependency Injection + injectable_generator:injectable_config_builder: + generate_for: + include: + - lib/injection/injector.dart + + injectable_generator:injectable_builder: + generate_for: + include: + - lib/injection/injector.dart + - lib/injection/modules/*_module.dart + + # data + - lib/data/**_config.dart + - lib/data/**_client.dart + - lib/data/**/data_sources/remote/*_remote_data_source.dart + - lib/data/services/**_service_impl.dart + - lib/data/services/**_remapper.dart + - lib/data/preferences/**_preferences.dart + + # domain + - lib/domain/use_cases/**_use_case.dart + + # presentation + - lib/presentation/features/**_cubit.dart + + # Routing + auto_route_generator: + generate_for: + include: + - lib/presentation/routes/router.dart + - lib/presentation/features/**_screen.dart + - lib/presentation/features/**_page.dart diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..7e7e7f6 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/ios/.gitignore b/ios/.gitignore index e96ef60..7a7f987 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..8c6e561 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Podfile b/ios/Podfile index 88359b2..279576f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 53f1382..566ba09 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -80,7 +80,6 @@ F324434539B9030C1AAD3483 /* Pods-Runner.profile-production.xcconfig */, F8CBFEC6A21C8B030120D56D /* Pods-Runner.profile-staging.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -168,7 +167,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -216,8 +215,8 @@ buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( @@ -257,6 +256,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -267,6 +267,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -277,7 +278,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -354,7 +355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -367,7 +368,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Development"; + APP_DISPLAY_NAME = "[DEV] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -377,7 +378,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.development; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.development; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -432,7 +433,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -481,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -496,7 +497,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Development"; + APP_DISPLAY_NAME = "[DEV] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -506,7 +507,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.development; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.development; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -519,7 +520,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Development"; + APP_DISPLAY_NAME = "[DEV] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -529,7 +530,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.development; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.development; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -584,7 +585,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -596,7 +597,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Staging"; + APP_DISPLAY_NAME = "[STG] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -606,7 +607,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.staging; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.staging; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -656,7 +657,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -671,7 +672,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Staging"; + APP_DISPLAY_NAME = "[STG] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -681,7 +682,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.staging; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.staging; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -730,7 +731,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -743,7 +744,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name Staging"; + APP_DISPLAY_NAME = "[STG] Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -753,7 +754,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate.staging; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter.staging; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -808,7 +809,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -820,7 +821,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name"; + APP_DISPLAY_NAME = "Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -830,7 +831,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -880,7 +881,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -895,7 +896,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name"; + APP_DISPLAY_NAME = "Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -905,7 +906,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -954,7 +955,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -967,7 +968,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - APP_DISPLAY_NAME = "Application Name"; + APP_DISPLAY_NAME = "Monstarlab Flutter Template"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -977,7 +978,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterTemplate; + PRODUCT_BUNDLE_IDENTIFIER = com.monstarlab.flutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..6266644 100644 --- a/ios/Runner/AppDelegate.swift +++ b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf0..7353c41 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd9..797d452 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b..6ed2d93 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde121..4cd7b00 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e..fe73094 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc230..321773c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd9..797d452 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8..502f463 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b86..0ec3034 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b86..0ec3034 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d16..e9f5fea 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d..84ac32a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41..8953cba 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f58..0467bf1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/config/dart_define.dart b/lib/config/dart_define.dart new file mode 100644 index 0000000..6ac6527 --- /dev/null +++ b/lib/config/dart_define.dart @@ -0,0 +1,5 @@ +// This class is declared as an `abstract final` to restrict inheritance +// and prevent instantiation outside the library. +abstract final class DartDefine { + static const apiBaseUrl = String.fromEnvironment('API_BASE_URL'); +} diff --git a/lib/config/dart_define_keys/dart_define_development_keys.json b/lib/config/dart_define_keys/dart_define_development_keys.json new file mode 100644 index 0000000..04abb7a --- /dev/null +++ b/lib/config/dart_define_keys/dart_define_development_keys.json @@ -0,0 +1,3 @@ +{ + "API_BASE_URL": "https://randomuser.me" +} diff --git a/lib/config/dart_define_keys/dart_define_production_keys.json b/lib/config/dart_define_keys/dart_define_production_keys.json new file mode 100644 index 0000000..04abb7a --- /dev/null +++ b/lib/config/dart_define_keys/dart_define_production_keys.json @@ -0,0 +1,3 @@ +{ + "API_BASE_URL": "https://randomuser.me" +} diff --git a/lib/config/dart_define_keys/dart_define_staging_keys.json b/lib/config/dart_define_keys/dart_define_staging_keys.json new file mode 100644 index 0000000..04abb7a --- /dev/null +++ b/lib/config/dart_define_keys/dart_define_staging_keys.json @@ -0,0 +1,3 @@ +{ + "API_BASE_URL": "https://randomuser.me" +} diff --git a/lib/data/api/api_config.dart b/lib/data/api/api_config.dart index 5fbc7f7..d26a2b0 100644 --- a/lib/data/api/api_config.dart +++ b/lib/data/api/api_config.dart @@ -1,10 +1,12 @@ -import 'package:injectable/injectable.dart'; - -@Injectable() +/// A configuration class providing properties specific to an API. +/// +/// Class instance is registered via [ApiConfigModule]. class ApiConfig { - final String baseUrl; + ApiConfig({ + required this.baseUrl, + }); - ApiConfig(this.baseUrl); + final String baseUrl; String get apiUrl => '$baseUrl/api'; } diff --git a/lib/data/services/http_client/dio_http_client.dart b/lib/data/core/http_client/dio_http_client.dart similarity index 91% rename from lib/data/services/http_client/dio_http_client.dart rename to lib/data/core/http_client/dio_http_client.dart index acbd98e..8b25922 100644 --- a/lib/data/services/http_client/dio_http_client.dart +++ b/lib/data/core/http_client/dio_http_client.dart @@ -1,13 +1,14 @@ import 'package:dio/dio.dart'; -import 'package:flutter_template/data/services/http_client/http_client.dart'; -import 'package:flutter_template/data/services/response_error.dart'; +import 'package:flutter_template/data/core/http_client/http_client.dart'; +import 'package:flutter_template/domain/common/response_error/response_error.dart'; +import 'package:flutter_template/injection/modules/network_module.dart'; import 'package:injectable/injectable.dart'; /// Abstraction of the Dio http client class. @Injectable(as: HttpClient) class DioHttpClient extends HttpClient { - DioHttpClient(this.dio); + DioHttpClient(@Named(dioForAuthentication) this.dio); final Dio dio; diff --git a/lib/data/services/http_client/dio_http_client_builder.dart b/lib/data/core/http_client/dio_http_client_builder.dart similarity index 93% rename from lib/data/services/http_client/dio_http_client_builder.dart rename to lib/data/core/http_client/dio_http_client_builder.dart index 26ef652..3f20393 100644 --- a/lib/data/services/http_client/dio_http_client_builder.dart +++ b/lib/data/core/http_client/dio_http_client_builder.dart @@ -1,5 +1,5 @@ import 'package:dio/dio.dart'; -import 'package:flutter_template/data/services/http_client/dio_http_client.dart'; +import 'package:flutter_template/data/core/http_client/dio_http_client.dart'; DioHttpClient? _dioClient; diff --git a/lib/data/services/http_client/http_client.dart b/lib/data/core/http_client/http_client.dart similarity index 100% rename from lib/data/services/http_client/http_client.dart rename to lib/data/core/http_client/http_client.dart diff --git a/lib/data/interceptor/auth_interceptor.dart b/lib/data/interceptors/auth_interceptor.dart similarity index 91% rename from lib/data/interceptor/auth_interceptor.dart rename to lib/data/interceptors/auth_interceptor.dart index 88d4263..771a0ae 100644 --- a/lib/data/interceptor/auth_interceptor.dart +++ b/lib/data/interceptors/auth_interceptor.dart @@ -1,15 +1,15 @@ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_template/data/interceptor/meta_interceptor.dart'; +import 'package:flutter_template/data/core/http_client/dio_http_client.dart'; +import 'package:flutter_template/data/core/http_client/http_client.dart'; +import 'package:flutter_template/data/interceptors/meta_interceptor.dart'; import 'package:flutter_template/data/model/auth/auth_tokens.dart'; import 'package:flutter_template/data/preferences/auth_preferences.dart'; -import 'package:flutter_template/data/services/http_client/dio_http_client.dart'; -import 'package:flutter_template/data/services/http_client/http_client.dart'; -import 'package:flutter_template/data/services/response_error.dart'; -import 'package:flutter_template/data/services/response_objects/tokens_response.dart'; +import 'package:flutter_template/data/response_objects/tokens_response.dart'; +import 'package:flutter_template/domain/common/response_error/response_error.dart'; import 'package:flutter_template/domain/preferences/user_preferences.dart'; -class AuthInterceptor extends InterceptorsWrapper { +class AuthInterceptor extends QueuedInterceptor { AuthInterceptor({ required this.httpClient, required this.onTokenExpired, @@ -116,7 +116,7 @@ class AuthInterceptor extends InterceptorsWrapper { headers: { 'Authorization': 'Bearer ${authPreferences.refreshToken}', MetaInterceptor.nMetaHeaderKey: - requestOptions.headers[MetaInterceptor.nMetaHeaderKey] + requestOptions.headers[MetaInterceptor.nMetaHeaderKey], }, ); diff --git a/lib/data/interceptor/meta_interceptor.dart b/lib/data/interceptors/meta_interceptor.dart similarity index 97% rename from lib/data/interceptor/meta_interceptor.dart rename to lib/data/interceptors/meta_interceptor.dart index e414708..5ca5d6a 100644 --- a/lib/data/interceptor/meta_interceptor.dart +++ b/lib/data/interceptors/meta_interceptor.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_template/presentation/app_flavor.dart'; import 'package:package_info_plus/package_info_plus.dart'; -class MetaInterceptor extends InterceptorsWrapper { +class MetaInterceptor extends QueuedInterceptor { MetaInterceptor(this.flavor); static String nMetaHeaderKey = 'n-meta'; diff --git a/lib/data/preferences/user_shared_preferences.dart b/lib/data/preferences/user_shared_preferences.dart index 4012d4b..1c124a9 100644 --- a/lib/data/preferences/user_shared_preferences.dart +++ b/lib/data/preferences/user_shared_preferences.dart @@ -7,8 +7,8 @@ import 'package:shared_preferences/shared_preferences.dart'; /// Store the current authenticated user's basic information. -@LazySingleton() -class UserSharedPreferences extends UserPreferences { +@LazySingleton(as: UserPreferences) +class UserSharedPreferences implements UserPreferences { UserSharedPreferences(this._preferences); final SharedPreferences _preferences; diff --git a/lib/data/services/response_objects/error_response.dart b/lib/data/response_objects/error_response.dart similarity index 61% rename from lib/data/services/response_objects/error_response.dart rename to lib/data/response_objects/error_response.dart index d5e0a44..c45a0ff 100644 --- a/lib/data/services/response_objects/error_response.dart +++ b/lib/data/response_objects/error_response.dart @@ -1,13 +1,12 @@ -import 'package:flutter_template/data/services/response_error.dart'; -import 'package:flutter_template/nstack/nstack.dart'; +import 'package:flutter_template/domain/common/response_error/response_error.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'error_response.freezed.dart'; part 'error_response.g.dart'; -///The BE should provide one unique code for each error, in this case the -///error is being provided through errorName so we create an Enum for each -///code we are aware of with a default value to unknown +/// The BE should provide one unique code for each error, in this case the +/// error is being provided through errorName so we create an Enum for each +/// code we are aware of with a default value to unknown @freezed class APIError with _$APIError { const factory APIError({ @@ -27,18 +26,18 @@ class APIError with _$APIError { @freezed class ErrorResponse with _$ErrorResponse { const factory ErrorResponse({ - //TODO: name this according to the map key from the error response - //if no map key exists then just create this factory with the APIError values - //and remove APIError, + // TODO: name this according to the map key from the error response + // if no map key exists then just create this factory with the APIError values + // and remove APIError, required APIError error, }) = _ErrorResponse; factory ErrorResponse.fromJson(Map json) => - _$$_ErrorResponseFromJson(json); + _$ErrorResponseFromJson(json); } enum ErrorName { - //Add error enum and the BE value + // Add error enum and the BE value @JsonValue('errorExample') errorExample, unknown, @@ -53,13 +52,13 @@ extension ErrorResponseExtensions on ErrorResponse { } extension ErrorNameExtensions on ErrorName { - String getErrorMessage(Localization l10n) { + String getErrorMessage() { switch (this) { - //Handle error enum and return mapped nstack vlaue + // TODO: Handle error enum and return mapped localized value case ErrorName.errorExample: - return l10n.error.authenticationError; + return 'Error Occurred'; default: - return ''; + return 'Unexpected Error'; } } } diff --git a/lib/data/services/response_objects/tokens_response.dart b/lib/data/response_objects/tokens_response.dart similarity index 97% rename from lib/data/services/response_objects/tokens_response.dart rename to lib/data/response_objects/tokens_response.dart index a08f9e0..b526276 100644 --- a/lib/data/services/response_objects/tokens_response.dart +++ b/lib/data/response_objects/tokens_response.dart @@ -22,7 +22,7 @@ class TokensResponse with _$TokensResponse { 'accessToken': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwY2MiO', 'refreshToken': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwY2Mi', 'tokenType': 'Bearer', - 'expiresIn': 2592000 + 'expiresIn': 2592000, }); } diff --git a/lib/data/services/http_profile_service.dart b/lib/data/services/http_profile_service.dart deleted file mode 100644 index 53447b4..0000000 --- a/lib/data/services/http_profile_service.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter_template/domain/services/profile_service.dart'; -import 'package:injectable/injectable.dart'; - -@LazySingleton(as: ProfileService) -class HttpProfileService extends ProfileService { - HttpProfileService(); - - @override - Future getProfileName() async { - // TODO: implement getProfileName - await Future.delayed(const Duration(milliseconds: 800)); - return 'Michael Laudrup'; - } -} diff --git a/lib/data/services/profile/data_sources/remote/profile_remote_data_source.dart b/lib/data/services/profile/data_sources/remote/profile_remote_data_source.dart new file mode 100644 index 0000000..0a80159 --- /dev/null +++ b/lib/data/services/profile/data_sources/remote/profile_remote_data_source.dart @@ -0,0 +1,19 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_template/data/services/profile/dtos/profile_response.dart'; +import 'package:flutter_template/injection/modules/network_module.dart'; +import 'package:injectable/injectable.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'profile_remote_data_source.g.dart'; + +@RestApi() +@lazySingleton +abstract class ProfileRemoteDataSource { + @factoryMethod + factory ProfileRemoteDataSource( + @Named(dioAuthenticated) Dio dio, + ) = _ProfileRemoteDataSource; + + @GET('/') + Future getProfile(); +} diff --git a/lib/data/services/profile/dtos/profile_response.dart b/lib/data/services/profile/dtos/profile_response.dart new file mode 100644 index 0000000..f95b770 --- /dev/null +++ b/lib/data/services/profile/dtos/profile_response.dart @@ -0,0 +1,40 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile_response.freezed.dart'; +part 'profile_response.g.dart'; + +@freezed +class ProfileResponse with _$ProfileResponse { + const factory ProfileResponse({ + List? results, + }) = _ProfileResponse; + + factory ProfileResponse.fromJson(Map json) => + _$ProfileResponseFromJson(json); +} + +@freezed +class ProfileResultDto with _$ProfileResultDto { + const factory ProfileResultDto({ + String? gender, + NameDto? name, + String? email, + String? phone, + String? cell, + }) = _ProfileResultDto; + + factory ProfileResultDto.fromJson(Map json) => + _$ProfileResultDtoFromJson(json); +} + +@freezed +class NameDto with _$NameDto { + const factory NameDto({ + String? title, + String? first, + String? last, + }) = _NameDto; + + factory NameDto.fromJson(Map json) => + _$NameDtoFromJson(json); +} diff --git a/lib/data/services/profile/profile_service_impl.dart b/lib/data/services/profile/profile_service_impl.dart new file mode 100644 index 0000000..b9a036f --- /dev/null +++ b/lib/data/services/profile/profile_service_impl.dart @@ -0,0 +1,24 @@ +import 'package:flutter_template/data/services/profile/data_sources/remote/profile_remote_data_source.dart'; +import 'package:flutter_template/data/services/profile/remapper/profile_remapper.dart'; +import 'package:flutter_template/domain/entities/profile/profile_entity.dart'; +import 'package:flutter_template/domain/services/profile/profile_service.dart'; +import 'package:injectable/injectable.dart'; + +@Injectable(as: ProfileService) +class ProfileServiceImpl implements ProfileService { + ProfileServiceImpl( + this._profileRemoteDataSource, + this._profileRemapper, + ); + + final ProfileRemoteDataSource _profileRemoteDataSource; + final ProfileRemapper _profileRemapper; + + @override + Future getProfile() async { + final response = await _profileRemoteDataSource.getProfile(); + return _profileRemapper.fromResponse( + response, + ); + } +} diff --git a/lib/data/services/profile/remapper/profile_remapper.dart b/lib/data/services/profile/remapper/profile_remapper.dart new file mode 100644 index 0000000..25aa1d7 --- /dev/null +++ b/lib/data/services/profile/remapper/profile_remapper.dart @@ -0,0 +1,15 @@ +import 'package:flutter_template/data/services/profile/dtos/profile_response.dart'; +import 'package:flutter_template/domain/entities/profile/profile_entity.dart'; +import 'package:injectable/injectable.dart'; + +@lazySingleton +class ProfileRemapper { + ProfileEntity fromResponse(ProfileResponse response) { + final personName = response.results?.first.name; + + return ProfileEntity( + firstName: personName?.first ?? '', + lastName: personName?.last ?? '', + ); + } +} diff --git a/lib/domain/common/base_status/base_status.dart b/lib/domain/common/base_status/base_status.dart new file mode 100644 index 0000000..40f5821 --- /dev/null +++ b/lib/domain/common/base_status/base_status.dart @@ -0,0 +1,31 @@ +import 'package:flutter_template/domain/common/response_error/response_error.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'base_status.freezed.dart'; + +@freezed +sealed class BaseStatus with _$BaseStatus { + const BaseStatus._(); + + const factory BaseStatus.initial() = Initial; + + const factory BaseStatus.loading() = Loading; + + const factory BaseStatus.lazyLoading() = LazyLoading; + + const factory BaseStatus.success() = Success; + + const factory BaseStatus.valid() = Valid; + + const factory BaseStatus.invalid() = Invalid; + + const factory BaseStatus.failure(ResponseError error) = Failure; + + bool get isInitial => this is Initial; + bool get isLoading => this is Loading; + bool get isLazyLoading => this is LazyLoading; + bool get isSuccess => this is Success; + bool get isValid => this is Valid; + bool get isInvalid => this is Invalid; + bool get isFailure => this is Failure; +} diff --git a/lib/data/services/response_error.dart b/lib/domain/common/response_error/response_error.dart similarity index 71% rename from lib/data/services/response_error.dart rename to lib/domain/common/response_error/response_error.dart index 2299593..f09e311 100644 --- a/lib/data/services/response_error.dart +++ b/lib/domain/common/response_error/response_error.dart @@ -2,8 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_template/data/services/response_objects/error_response.dart'; -import 'package:flutter_template/nstack/nstack.dart'; +import 'package:flutter_template/data/response_objects/error_response.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'response_error.freezed.dart'; @@ -13,7 +12,7 @@ part 'response_error.freezed.dart'; /// /// We return those errors to get localized messages to display to the user. @freezed -class ResponseError with _$ResponseError implements Exception { +sealed class ResponseError with _$ResponseError implements Exception { const ResponseError._(); const factory ResponseError.noInternetConnection() = _NoInternetConnection; @@ -35,7 +34,7 @@ class ResponseError with _$ResponseError implements Exception { const factory ResponseError.invalidEmail() = _InvalidEmailError; const factory ResponseError.invalidLoginCredentials() = _InvalidLoginCredentials; - const factory ResponseError.invalidSearhTerm() = _InvalidSearchTermError; + const factory ResponseError.invalidSearchTerm() = _InvalidSearchTermError; static ResponseError from(Object error) { if (error is ResponseError) { @@ -91,28 +90,27 @@ class ResponseError with _$ResponseError implements Exception { } extension ResponseErrorExtensions on ResponseError { - String getErrorMessage(Localization l10n) { - //TODO: create error module for errors and set value accordingly + String getErrorMessage() { return when( - noInternetConnection: () => l10n.error.connectionError, - sendTimeout: () => l10n.error.authenticationError, - connectTimeout: () => l10n.error.authenticationError, - receiveTimeout: () => l10n.error.authenticationError, - badRequest: (message) => message.getErrorMessage(l10n), - notFound: () => l10n.error.authenticationError, - tooManyRequests: () => l10n.error.authenticationError, - unprocessableEntity: () => l10n.error.authenticationError, - internalServerError: () => l10n.error.authenticationError, - unexpectedError: () => l10n.error.authenticationError, - requestCancelled: () => l10n.error.authenticationError, - conflict: () => l10n.error.authenticationError, - unauthorized: () => l10n.error.authenticationError, - invalidPassword: () => l10n.error.authenticationError, - invalidEmail: () => l10n.error.authenticationError, - invalidSearhTerm: () => l10n.error.authenticationError, - invalidLoginCredentials: () => l10n.error.authenticationError, - badCertificate: () => l10n.error.authenticationError, - connectionError: () => l10n.error.connectionError, + noInternetConnection: () => 'No Internet Connection', + sendTimeout: () => 'Send Timeout Error', + connectTimeout: () => 'Connection Timeout Error', + receiveTimeout: () => 'Receive Timeout Error', + badRequest: (message) => message.getErrorMessage(), + notFound: () => 'Not Found Error', + tooManyRequests: () => 'Too Many Requests', + unprocessableEntity: () => 'Unprocessable Entity', + internalServerError: () => 'Internal Server Error', + unexpectedError: () => 'Unexpected Error', + requestCancelled: () => 'Request Cancelled', + conflict: () => 'Conflict Error', + unauthorized: () => 'Unauthorized Error', + invalidPassword: () => 'Invalid Password', + invalidEmail: () => 'Invalid Email', + invalidSearchTerm: () => 'Invalid Search Term', + invalidLoginCredentials: () => 'Invalid Login Credentials', + badCertificate: () => 'Bad Certificate', + connectionError: () => 'Connection Error', ); } } diff --git a/lib/domain/common/use_case.dart b/lib/domain/common/use_case.dart deleted file mode 100644 index 785c33c..0000000 --- a/lib/domain/common/use_case.dart +++ /dev/null @@ -1,173 +0,0 @@ -/// Future output use case that would take in an [Input] and return [Output]. -/// -/// Used when we need to consider any inputs like filtering parameters etc. -/// -/// Example: -/// // import tuple -/// typedef Input = Tuple, Query>; -/// -/// ```dart -/// class QueryStoriesUseCase extends UseCase> { -/// @override -/// List run(Input input) { -/// final stories = input.item1; -/// final query = input.item2; -/// return stories.where((story) => story.name.contains(query.name)); -/// } -/// } -/// -/// class Query { -/// final int age; -/// final String name; -/// final String author; -/// -/// Query(this.age, this.name, this.author); -/// } -/// ``` -/// -abstract class UseCase { - Output run(Input input); -} - -/// Output use case that would return [Output]. -/// -/// Used when we don't need to consider any inputs like filtering parameters etc. -/// -/// Example: -/// -/// ```dart -/// class AgeValidationUseCase extends OutputUseCase { -/// AgeValidationUseCase({ -/// required this._user, -/// }); -/// -/// final User _user; -/// -/// @override -/// bool run() { -/// return _user.age >= 18; -/// } -/// } -/// ``` -/// -abstract class OutputUseCase { - Output run(); -} - -/// Stream use case that would return [Output] as a [Stream]. -/// -/// Used when we want to get an async sequence of data. -/// -/// -/// Example: -/// -/// ```dart -/// class ObserveStoryPlayerStateUseCase extends StreamOutputUseCase { -/// ObserveStoryPlayerStateUseCase({ -/// required this._storyPlayerPreferences, -/// }); -/// -/// final StoryPlayerPreferences _storyPlayerPreferences; -/// -/// @override -/// Stream run() { -/// return _storyPlayerPreferences -/// .getStoryPlayerStateStream() -/// .where((storyPlayerState) => storyPlayerState.hasStoryForPlayback); -/// } -/// } -/// ``` -/// -abstract class StreamOutputUseCase - extends OutputUseCase> {} - -/// Stream output use case that would take in an [Input] and return [Output] as a [Stream]. -/// -/// Used when we want to get an async sequence of data. -/// -/// -/// Example: -/// -/// ```dart -/// class ObserveStoryPlayerStateFromStoryIdUseCase extends StreamUseCase { -/// ObserveStoryPlayerStateFromStoryIdUseCase( -/// this._storyPlayerPreferences, -/// ); -/// -/// final StoryPlayerPreferences _storyPlayerPreferences; -/// -/// @override -/// Stream run(Query input) { -/// return _storyPlayerPreferences -/// .getStoryPlayerStateStreamFromStoryID(input.storyID) -/// .where((storyPlayerState) => storyPlayerState.hasStoryForPlayback); -/// } -/// } -/// -/// class Query { -/// final String name; -/// final String author; -/// final int storyID; -/// -/// Query(this.name, this.author, this.storyID); -/// } -/// ``` -/// -abstract class StreamUseCase - extends UseCase> {} - -/// Future output use case that would take in an [Input] and return [Output] as a [Future]. -/// -/// Used when we need to consider any inputs like filtering parameters etc. -/// -/// Example: -/// -/// ```dart -/// class QueryStoriesUseCase extends FutureUseCase> { -/// QueryStoriesUseCase( -/// this._storiesService, -/// ); -/// -/// final StoriesService _storiesService; -/// -/// @override -/// Future> run(Query input) async { -/// return _storiesService.queryStories(input); -/// } -/// } -/// -/// class Query { -/// final int age; -/// final String name; -/// final String author; -/// -/// Query(this.age, this.name, this.author); -/// } -/// ``` -/// -abstract class FutureUseCase - extends UseCase> {} - -/// Future output use case that would return [Output] as a [Future]. -/// -/// Used when we don't need to consider any inputs like filtering parameters etc. -/// -/// Example: -/// -/// ```dart -/// class GetAllStoriesUseCase extends FutureOutputUseCase> { -/// GetAllStoriesUseCase( -/// this._storiesService, -/// ); -/// -/// final StoriesService _storiesService; -/// -/// @override -/// Future> run() async { -/// return _storiesService.fetchAllStories(); -/// } -/// } -/// ``` -/// -abstract class FutureOutputUseCase - extends OutputUseCase> {} diff --git a/lib/domain/entities/profile/profile_entity.dart b/lib/domain/entities/profile/profile_entity.dart new file mode 100644 index 0000000..a2ad9a3 --- /dev/null +++ b/lib/domain/entities/profile/profile_entity.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profile_entity.freezed.dart'; + +@freezed +class ProfileEntity with _$ProfileEntity { + const ProfileEntity._(); + + const factory ProfileEntity({ + required String firstName, + required String lastName, + }) = _ProfileEntity; + + String get fullName { + if (firstName.isEmpty && lastName.isEmpty) { + return 'N/A'; + } + + return '$firstName $lastName'.trim(); + } +} diff --git a/lib/domain/services/profile/profile_service.dart b/lib/domain/services/profile/profile_service.dart new file mode 100644 index 0000000..1a2bc6c --- /dev/null +++ b/lib/domain/services/profile/profile_service.dart @@ -0,0 +1,5 @@ +import 'package:flutter_template/domain/entities/profile/profile_entity.dart'; + +abstract interface class ProfileService { + Future getProfile(); +} diff --git a/lib/domain/services/profile_service.dart b/lib/domain/services/profile_service.dart deleted file mode 100644 index b869f18..0000000 --- a/lib/domain/services/profile_service.dart +++ /dev/null @@ -1,3 +0,0 @@ -abstract class ProfileService { - Future getProfileName(); -} diff --git a/lib/domain/use_cases/profile/get_profile_use_case.dart b/lib/domain/use_cases/profile/get_profile_use_case.dart new file mode 100644 index 0000000..4ce2ce8 --- /dev/null +++ b/lib/domain/use_cases/profile/get_profile_use_case.dart @@ -0,0 +1,14 @@ +import 'package:flutter_template/domain/entities/profile/profile_entity.dart'; +import 'package:flutter_template/domain/services/profile/profile_service.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +final class GetProfileUseCase { + GetProfileUseCase(this._profileService); + + final ProfileService _profileService; + + Future call() { + return _profileService.getProfile(); + } +} diff --git a/lib/extensions/future_extensions.dart b/lib/extensions/future_extensions.dart index 0451c4d..f6e171f 100644 --- a/lib/extensions/future_extensions.dart +++ b/lib/extensions/future_extensions.dart @@ -1,5 +1,5 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter_template/data/services/response_error.dart'; +import 'package:flutter_template/domain/common/response_error/response_error.dart'; extension FutureExtensions on Future { Future catchPrintError(Function onError) { diff --git a/lib/injection/data_module.dart b/lib/injection/data_module.dart deleted file mode 100644 index 30137e0..0000000 --- a/lib/injection/data_module.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:flutter_template/data/api/api_config.dart'; -import 'package:flutter_template/data/interceptor/auth_interceptor.dart'; -import 'package:flutter_template/injection/injector.dart'; -import 'package:injectable/injectable.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -@module -abstract class DataModule { - @singleton - Future get prefs => SharedPreferences.getInstance(); - - Dio getDio(ApiConfig apiConfig) { - final dio = Dio(); - dio.httpClientAdapter = HttpClientAdapter(); - dio.options.baseUrl = apiConfig.apiUrl; - dio.interceptors.add( - AuthInterceptor( - httpClient: injector(), - authPreferences: injector(), - userPreferences: injector(), - refreshTokenHttpClient: injector(), - onTokenExpired: () { - // TODO: Handle log out - }, - ), - ); - - return dio; - } -} diff --git a/lib/injection/dependencies.dart b/lib/injection/dependencies.dart index 47fe71f..4edfee9 100644 --- a/lib/injection/dependencies.dart +++ b/lib/injection/dependencies.dart @@ -1,13 +1,12 @@ +import 'package:flutter_template/injection/injector.dart'; import 'package:flutter_template/presentation/app_flavor.dart'; import 'package:flutter_template/presentation/routes/router.dart'; -import 'injector.dart'; - class DependencyManager { static Future inject(AppFlavor flavor) async { injector.registerLazySingleton(() => flavor); + // ignore: unnecessary_lambdas injector.registerLazySingleton(() => AppRouter()); - - await configureDependencies(); + return configureDependencies(); } } diff --git a/lib/injection/injector.dart b/lib/injection/injector.dart index dc5032f..50b4e94 100644 --- a/lib/injection/injector.dart +++ b/lib/injection/injector.dart @@ -2,12 +2,7 @@ import 'package:flutter_template/injection/injector.config.dart'; import 'package:get_it/get_it.dart'; import 'package:injectable/injectable.dart'; -GetIt injector = GetIt.instance; - -@InjectableInit( - initializerName: r'$initGetIt', // default - preferRelativeImports: true, // default - asExtension: false, // default -) -Future configureDependencies() async => $initGetIt(injector); +GetIt injector = GetIt.I; +@injectableInit +Future configureDependencies() => injector.init(); diff --git a/lib/injection/modules/api_config_module.dart b/lib/injection/modules/api_config_module.dart new file mode 100644 index 0000000..fc45cb1 --- /dev/null +++ b/lib/injection/modules/api_config_module.dart @@ -0,0 +1,15 @@ +import 'package:flutter_template/config/dart_define.dart'; +import 'package:flutter_template/data/api/api_config.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class ApiConfigModule { + @singleton + ApiConfig getApiConfig() { + final apiConfig = ApiConfig( + baseUrl: DartDefine.apiBaseUrl, + ); + + return apiConfig; + } +} diff --git a/lib/injection/modules/data_module.dart b/lib/injection/modules/data_module.dart new file mode 100644 index 0000000..dea8319 --- /dev/null +++ b/lib/injection/modules/data_module.dart @@ -0,0 +1,15 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +@module +abstract class DataModule { + // If we want to pre-await the future and register its resolved value, + // we should annotate our async dependencies with @preResolve. + @preResolve + @singleton + Future get preferences => SharedPreferences.getInstance(); + + @singleton + FlutterSecureStorage get secureStorage => const FlutterSecureStorage(); +} diff --git a/lib/injection/modules/network_module.dart b/lib/injection/modules/network_module.dart new file mode 100644 index 0000000..70b78dd --- /dev/null +++ b/lib/injection/modules/network_module.dart @@ -0,0 +1,40 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_template/data/api/api_config.dart'; +import 'package:injectable/injectable.dart'; + +/// For authentication (sign in). +const dioForAuthentication = 'DIO_FOR_AUTHENTICATION'; + +/// For the authenticated endpoints. +const dioAuthenticated = 'DIO_AUTHENTICATED'; + +Dio _createBaseDio(ApiConfig apiConfig) { + final dio = Dio() + ..httpClientAdapter = HttpClientAdapter() + ..options.baseUrl = apiConfig.apiUrl; + + return dio; +} + +@module +abstract class NetworkModule { + @singleton + @Named(dioForAuthentication) + Dio getForAuthenticationDio( + ApiConfig apiConfig, + ) { + final dio = _createBaseDio(apiConfig); + // TODO: Add interceptors here + return dio; + } + + @singleton + @Named(dioAuthenticated) + Dio getAuthenticatedDio( + ApiConfig apiConfig, + ) { + final dio = _createBaseDio(apiConfig); + // TODO: Add interceptors here + return dio; + } +} diff --git a/lib/main_common.dart b/lib/main_common.dart index 7b3da26..7e808d4 100644 --- a/lib/main_common.dart +++ b/lib/main_common.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_template/injection/dependencies.dart'; import 'package:flutter_template/presentation/app.dart'; - -import 'presentation/app_flavor.dart'; +import 'package:flutter_template/presentation/app_flavor.dart'; +import 'package:flutter_template/presentation/common/assets_cache_manager.dart'; // ignore: avoid_void_async void mainCommon(AppFlavor flavor) async { WidgetsFlutterBinding.ensureInitialized(); await DependencyManager.inject(flavor); + await AssetsCachingManager.cacheAnimationsRoot(); runApp(const App()); } diff --git a/lib/main_development.dart b/lib/main_development.dart index 9b61cea..f756cf6 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -1,5 +1,5 @@ -import 'presentation/app_flavor.dart'; -import 'main_common.dart'; +import 'package:flutter_template/main_common.dart'; +import 'package:flutter_template/presentation/app_flavor.dart'; void main() async { const config = AppFlavor.development; diff --git a/lib/main_production.dart b/lib/main_production.dart index b77f0d5..e2691e8 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -1,5 +1,5 @@ -import 'presentation/app_flavor.dart'; -import 'main_common.dart'; +import 'package:flutter_template/main_common.dart'; +import 'package:flutter_template/presentation/app_flavor.dart'; void main() async { const config = AppFlavor.production; diff --git a/lib/main_staging.dart b/lib/main_staging.dart index d934f26..8571870 100644 --- a/lib/main_staging.dart +++ b/lib/main_staging.dart @@ -1,5 +1,5 @@ -import 'presentation/app_flavor.dart'; -import 'main_common.dart'; +import 'package:flutter_template/main_common.dart'; +import 'package:flutter_template/presentation/app_flavor.dart'; void main() async { const config = AppFlavor.staging; diff --git a/lib/nstack/nstack.json b/lib/nstack/nstack.json deleted file mode 100644 index 53a5eed..0000000 --- a/lib/nstack/nstack.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "version": 1, - "nstack_project_id": "k78R0OadfzZQmlhvKYkF1znwdQYShK1SCiLl", - "nstack_api_key": "e5tzJcRuIE3UsivDicjQZlI03TLgpWPEroOf" -} diff --git a/lib/presentation/app.dart b/lib/presentation/app.dart index a494429..e94710f 100644 --- a/lib/presentation/app.dart +++ b/lib/presentation/app.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_template/injection/injector.dart'; import 'package:flutter_template/presentation/resources/resources.dart'; import 'package:flutter_template/presentation/routes/router.dart'; -import '../../nstack/nstack.dart'; class App extends StatelessWidget { const App({Key? key}) : super(key: key); @@ -11,18 +10,15 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { final appRouter = injector.get(); + return MaterialApp.router( debugShowCheckedModeBanner: false, - theme: getAppTheme(Brightness.light), - darkTheme: getAppTheme(Brightness.dark), - title: 'Project Name', - builder: (c, widget) { - if (widget == null) return const SizedBox(); - - return NStackWidget( - child: widget, - ); - }, + theme: AppTheme.fromBrightness(Brightness.light), + darkTheme: AppTheme.fromBrightness(Brightness.dark), + // TODO: Set to [ThemeMode.light] if your app only supports light mode + themeMode: ThemeMode.system, + // TODO: Change Project Name Here + title: 'Monstarlab Flutter Template', routerDelegate: AutoRouterDelegate( appRouter, ), diff --git a/lib/presentation/common/assets_cache_manager.dart b/lib/presentation/common/assets_cache_manager.dart new file mode 100644 index 0000000..fcf74ac --- /dev/null +++ b/lib/presentation/common/assets_cache_manager.dart @@ -0,0 +1,21 @@ +abstract final class AssetsCachingManager { + /// Caches only the animations required at the initial app startup, + /// specifically for the splash screen. + /// + /// This method should be called before [runApp] to ensure that essential + /// animations are loaded early, improving the user experience during + /// the app's launch phase. + /// + /// By caching only necessary animations, this method reduces memory usage + /// and improves performance. + /// + /// Typically invoked during app initialization to enhance startup efficiency. + static Future cacheAnimationsRoot() async { + // Example: + // try { + // await AssetLottie(Assets.animations.splash.path).load(); + // } catch (e) { + // // Nothing to do + // } + } +} diff --git a/lib/presentation/extensions/color_extensions.dart b/lib/presentation/extensions/color_extensions.dart new file mode 100644 index 0000000..c22ab22 --- /dev/null +++ b/lib/presentation/extensions/color_extensions.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +extension ColorExtensions on Color { + /// Returns a new color with reduced opacity. + /// + /// The resulting color is a copy of the original color with an opacity + /// level of 0.44, making it suitable for creating a visually muted or + /// disabled appearance. + /// + /// Example: + /// ```dart + /// final myColor = Colors.blue; + /// final mutedColor = myColor.lowOpacity(); + /// ``` + Color lowOpacity() { + return withOpacity(0.44); + } +} diff --git a/lib/presentation/extensions/presentation_extensions.dart b/lib/presentation/extensions/presentation_extensions.dart index 15b5cd9..314a562 100644 --- a/lib/presentation/extensions/presentation_extensions.dart +++ b/lib/presentation/extensions/presentation_extensions.dart @@ -1,4 +1,6 @@ -// A set of extensions that can be used on the presentation layer. +/// A set of extensions that can be used on the presentation layer. library presentation_extensions; +export 'color_extensions.dart'; +export 'scroll_controller_extensions.dart'; export 'string_presentation_extensions.dart'; diff --git a/lib/presentation/extensions/scroll_controller_extensions.dart b/lib/presentation/extensions/scroll_controller_extensions.dart new file mode 100644 index 0000000..5f83402 --- /dev/null +++ b/lib/presentation/extensions/scroll_controller_extensions.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/app_ui_constants.dart'; + +extension ScrollControllerExtensions on ScrollController { + Future appAnimateTo( + double offset, { + Duration duration = AppUiConstants.animationDuration, + Curve curve = AppUiConstants.transitionCurve, + }) { + return animateTo(offset, duration: duration, curve: curve); + } + + Future animateToTop({ + Duration duration = AppUiConstants.animationDuration, + Curve curve = Curves.fastEaseInToSlowEaseOut, + }) { + return appAnimateTo(0, duration: duration, curve: curve); + } +} diff --git a/lib/presentation/feature/home/home_screen.dart b/lib/presentation/feature/home/home_screen.dart deleted file mode 100644 index b528350..0000000 --- a/lib/presentation/feature/home/home_screen.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_template/presentation/feature/home/home_screen_tab.dart'; -import 'package:flutter_template/presentation/feature/news/news_page.dart'; -import 'package:flutter_template/presentation/feature/profile/profile_page.dart'; -import 'package:flutter_template/presentation/resources/resources.dart'; - -@RoutePage() -class HomeScreen extends StatefulWidget { - const HomeScreen({ - Key? key, - this.tab = HomeScreenTab.news, - }) : super(key: key); - - final HomeScreenTab tab; - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - HomeScreenTab? _tabSelection; - - @override - void initState() { - _tabSelection = widget.tab; - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: context.colors.background, - body: _getSelectedPage(_tabSelection), - bottomNavigationBar: BottomNavigationBar( - backgroundColor: context.colors.surface, - unselectedItemColor: context.colors.text, - items: _getBottomNavigationBarItems(context), - currentIndex: _tabSelection!.index, - type: BottomNavigationBarType.fixed, - onTap: (index) => setState(() { - _tabSelection = HomeScreenTab.values[index]; - }), - ), - ); - } - - Widget _getSelectedPage(HomeScreenTab? tab) { - switch (tab) { - case HomeScreenTab.news: - return const NewsPage(); - case HomeScreenTab.profile: - return const ProfilePage(); - default: - throw ('Unknown HomeScreenTab'); - } - } - - List _getBottomNavigationBarItems( - BuildContext context, - ) { - return [ - const BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'News', - ), - const BottomNavigationBarItem( - icon: Icon(Icons.person_rounded), - label: 'Profile', - ), - ]; - } -} diff --git a/lib/presentation/feature/home/home_screen_tab.dart b/lib/presentation/feature/home/home_screen_tab.dart deleted file mode 100644 index 78c43f3..0000000 --- a/lib/presentation/feature/home/home_screen_tab.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum HomeScreenTab { - news, - profile, -} diff --git a/lib/presentation/feature/profile/profile_cubit.dart b/lib/presentation/feature/profile/profile_cubit.dart deleted file mode 100644 index 70f0329..0000000 --- a/lib/presentation/feature/profile/profile_cubit.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_template/domain/services/profile_service.dart'; -import 'package:flutter_template/extensions/extensions.dart'; -import 'package:flutter_template/presentation/feature/profile/profile_state.dart'; -import 'package:injectable/injectable.dart'; - -@Injectable() -class ProfileCubit extends Cubit { - final ProfileService profileService; - - ProfileCubit({ - required this.profileService, - @factoryParam ProfileState? state, - }) : super(state ?? ProfileState.initial()); - - Future load() async { - if (state.isLoading) return; - - emit(state.copyWith(isLoading: true)); - - return profileService - .getProfileName() - .then( - (value) => emit( - state.copyWith(isLoading: false, name: value ?? ''), - ), - ) - .catchPrintError((e, s) { - emit( - state.copyWith( - isLoading: false, - name: '', - ), - ); - }); - } -} diff --git a/lib/presentation/feature/profile/profile_page.dart b/lib/presentation/feature/profile/profile_page.dart deleted file mode 100644 index 99cc6ec..0000000 --- a/lib/presentation/feature/profile/profile_page.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_template/injection/injector.dart'; -import 'package:flutter_template/presentation/feature/profile/profile_cubit.dart'; -import 'package:flutter_template/presentation/feature/profile/profile_state.dart'; -import 'package:flutter_template/presentation/resources/resources.dart'; - -class ProfilePage extends StatefulWidget { - const ProfilePage({Key? key}) : super(key: key); - - @override - State createState() => _ProfilePageState(); -} - -class _ProfilePageState extends State { - final _profilePresenter = injector.get(); - - @override - void initState() { - super.initState(); - _profilePresenter.load(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: context.colors.background, - appBar: AppBar( - backgroundColor: context.colors.accent, - title: BlocBuilder( - bloc: _profilePresenter, - builder: (context, state) { - return Text(state.isLoading ? 'Profile' : 'Profile: ${state.name}'); - }, - ), - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Center( - child: Column( - children: [ - BlocBuilder( - bloc: _profilePresenter, - builder: (context, state) { - if (state.isLoading) { - return const CircularProgressIndicator(); - } else { - return Text('Hi ${state.name}!'); - } - }) - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/presentation/features/_playground/playground_screen.dart b/lib/presentation/features/_playground/playground_screen.dart new file mode 100644 index 0000000..bf33d09 --- /dev/null +++ b/lib/presentation/features/_playground/playground_screen.dart @@ -0,0 +1,273 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/foundation.dart'; + +import 'package:flutter/material.dart'; +import 'package:flutter_sticky_header/flutter_sticky_header.dart'; +import 'package:flutter_template/presentation/resources/app_colors.dart'; +import 'package:flutter_template/presentation/resources/app_ui_constants.dart'; +import 'package:flutter_template/presentation/routes/router.gr.dart'; +import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart'; +import 'package:flutter_template/presentation/widgets/button/app_button.dart'; +import 'package:flutter_template/presentation/widgets/spacers/safe_area_spacer.dart'; +import 'package:flutter_template/presentation/widgets/text/app_text.dart'; +import 'package:flutter_template/presentation/widgets/utilities/app_screen_config.dart'; + +/// A playground screen designed for showcasing reusable widgets. +/// +/// This screen is intended for presenting and testing reusable widgets within +/// the context of a Flutter application. +/// +/// If you need to extract some small widgets only for this playground screen, +/// consider using the private modifier for widget classes. +@RoutePage() +class PlaygroundScreen extends StatelessWidget { + const PlaygroundScreen({super.key}); + + @override + Widget build(BuildContext context) { + const verticalGap = SizedBox(height: 12); + const sliverGap = SliverToBoxAdapter( + child: verticalGap, + ); + + return AppScreenConfig( + child: Scaffold( + appBar: const TopAppBar( + label: 'Playground', + ), + body: CustomScrollView( + slivers: [ + _PlaygroundStickyHeader( + label: 'Color Boxes', + child: Wrap( + children: [ + _PlaygroundColorBox( + label: 'Primary', + color: context.colors.primary, + labelColor: context.colors.foregroundOnPrimary, + ), + _PlaygroundColorBox( + label: 'Secondary', + color: context.colors.secondary, + labelColor: context.colors.foregroundOnSecondary, + ), + _PlaygroundColorBox( + label: 'Background', + color: context.colors.background, + labelColor: context.colors.foregroundOnBackground, + ), + _PlaygroundColorBox( + label: 'Danger', + color: context.colors.danger, + labelColor: context.colors.foregroundOnDanger, + ), + _PlaygroundColorBox( + label: 'Outline', + color: context.colors.outline, + labelColor: context.colors.foregroundOnPrimary, + ), + _PlaygroundColorBox( + label: 'Primary Variant', + color: context.colors.primaryVariant, + labelColor: context.colors.foregroundOnPrimary, + ), + _PlaygroundColorBox( + label: 'Secondary Variant', + color: context.colors.secondaryVariant, + labelColor: context.colors.foregroundOnSecondary, + ), + _PlaygroundColorBox( + label: 'Splash', + color: context.colors.splashColor, + labelColor: context.colors.foregroundOnBackground, + ), + ], + ), + ), + sliverGap, + _PlaygroundStickyHeader( + label: 'Buttons', + child: Column( + children: [ + AppButton.primary( + label: 'Primary Button', + onPressed: () {}, + ), + verticalGap, + AppButton.secondary( + label: 'Secondary Button', + onPressed: () {}, + ), + verticalGap, + AppButton.outlined( + label: 'Outlined Button', + onPressed: () {}, + ), + verticalGap, + AppButton.destructive( + label: 'Destructive Button', + onPressed: () {}, + ), + verticalGap, + AppButton.text( + label: 'Text Button', + onPressed: () {}, + ), + ], + ), + ), + sliverGap, + _PlaygroundStickyHeader( + label: 'Small Buttons', + child: Column( + children: [ + Row( + children: [ + Expanded( + child: AppButton.primary( + label: 'Small Primary Button', + isSmall: true, + onPressed: () {}, + ), + ), + const SizedBox(width: 12), + Expanded( + child: AppButton.secondary( + label: 'Small Secondary Button', + isSmall: true, + onPressed: () {}, + ), + ), + ], + ), + ], + ), + ), + sliverGap, + _PlaygroundStickyHeader( + label: 'Texts', + child: Column( + children: [ + AppText.header1('Header 1'), + verticalGap, + AppText.header2('Header 2'), + verticalGap, + AppText.header3('Header 3'), + verticalGap, + AppText.bodySmall('This is Body (small).'), + verticalGap, + AppText.body('This is Body (regular).'), + verticalGap, + AppText.bodyLarge('This is Body (large).'), + verticalGap, + AppText.buttonLabel('This is Button label.'), + verticalGap, + AppText.underlineText('This is underlined text.'), + verticalGap, + AppText.custom( + 'This is custom', + fontWeight: FontWeight.w700, + ), + ], + ), + ), + const SliverToBoxAdapter( + child: SizedBox( + height: 72, + ), + ), + const SliverToBoxAdapter( + child: SafeAreaSpacer(), + ), + ], + ), + ), + ); + } +} + +class _PlaygroundStickyHeader extends StatelessWidget { + const _PlaygroundStickyHeader({ + required this.label, + required this.child, + }); + + final String label; + final Widget child; + + @override + Widget build(BuildContext context) { + return SliverStickyHeader( + header: ColoredBox( + color: context.colors.background, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 12.0, + ).copyWith(bottom: 12.0), + child: AppText.header3(label), + ), + ), + sliver: SliverToBoxAdapter( + child: Padding( + padding: AppUiConstants.defaultScreenHorizontalPadding, + child: child, + ), + ), + ); + } +} + +class _PlaygroundColorBox extends StatelessWidget { + const _PlaygroundColorBox({ + required this.label, + required this.color, + required this.labelColor, + }); + + final String label; + final Color color; + final Color labelColor; + + final boxSize = 100.0; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(6.0), + child: Container( + width: boxSize, + height: boxSize, + color: color, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: AppText.body( + label, + textAlign: TextAlign.center, + color: labelColor, + ), + ), + ), + ), + ); + } +} + +class PlaygroundScreenOpenerButton extends StatelessWidget { + const PlaygroundScreenOpenerButton({super.key}); + + @override + Widget build(BuildContext context) { + if (kReleaseMode) { + return const SizedBox.shrink(); + } + + return AppButton.text( + label: 'Open Playground Screen', + onPressed: () => context.pushRoute( + const PlaygroundRoute(), + ), + ); + } +} diff --git a/lib/presentation/features/home/home_page.dart b/lib/presentation/features/home/home_page.dart new file mode 100644 index 0000000..9714552 --- /dev/null +++ b/lib/presentation/features/home/home_page.dart @@ -0,0 +1,19 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/features/home/ui/home_body.dart'; +import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart'; + +@RoutePage() +class HomePage extends StatelessWidget { + const HomePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Scaffold( + appBar: TopAppBar( + label: 'Home', + ), + body: HomeBody(), + ); + } +} diff --git a/lib/presentation/features/home/ui/home_body.dart b/lib/presentation/features/home/ui/home_body.dart new file mode 100644 index 0000000..c8240c5 --- /dev/null +++ b/lib/presentation/features/home/ui/home_body.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class HomeBody extends StatelessWidget { + const HomeBody({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox(); + } +} diff --git a/lib/presentation/features/main/main_screen.dart b/lib/presentation/features/main/main_screen.dart new file mode 100644 index 0000000..833fe4f --- /dev/null +++ b/lib/presentation/features/main/main_screen.dart @@ -0,0 +1,32 @@ +import 'package:auto_route/auto_route.dart'; + +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/features/main/ui/app_bottom_navigation_bar.dart'; +import 'package:flutter_template/presentation/routes/router.gr.dart'; + +@RoutePage() +class MainScreen extends StatelessWidget { + const MainScreen({super.key}); + + @override + Widget build(BuildContext context) { + return AutoTabsScaffold( + routes: const [ + HomeRoute(), + NewsRoute(), + ProfileRoute(), + ], + animationDuration: Duration.zero, + bottomNavigationBuilder: (_, tabsRouter) { + return AppBottomNavigationBar( + tabsRouter: tabsRouter, + bottomNavigationBarItemList: const [ + BottomNavigationBarItem(label: 'Home', icon: Icon(Icons.home)), + BottomNavigationBarItem(label: 'News', icon: Icon(Icons.newspaper)), + BottomNavigationBarItem(label: 'Profile', icon: Icon(Icons.person)), + ], + ); + }, + ); + } +} diff --git a/lib/presentation/features/main/ui/app_bottom_navigation_bar.dart b/lib/presentation/features/main/ui/app_bottom_navigation_bar.dart new file mode 100644 index 0000000..0ce9bbf --- /dev/null +++ b/lib/presentation/features/main/ui/app_bottom_navigation_bar.dart @@ -0,0 +1,25 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/resources.dart'; + +class AppBottomNavigationBar extends StatelessWidget { + const AppBottomNavigationBar({ + super.key, + required this.tabsRouter, + required this.bottomNavigationBarItemList, + }); + + final TabsRouter tabsRouter; + final List bottomNavigationBarItemList; + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + backgroundColor: context.colors.background, + type: BottomNavigationBarType.fixed, + currentIndex: tabsRouter.activeIndex, + onTap: tabsRouter.setActiveIndex, + items: bottomNavigationBarItemList, + ); + } +} diff --git a/lib/presentation/feature/news/news_page.dart b/lib/presentation/features/news/news_page.dart similarity index 53% rename from lib/presentation/feature/news/news_page.dart rename to lib/presentation/features/news/news_page.dart index 5a2ade7..b0dfdf1 100644 --- a/lib/presentation/feature/news/news_page.dart +++ b/lib/presentation/features/news/news_page.dart @@ -1,6 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/features/news/ui/news_body.dart'; import 'package:flutter_template/presentation/resources/resources.dart'; +import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart'; +@RoutePage() class NewsPage extends StatelessWidget { const NewsPage({Key? key}) : super(key: key); @@ -8,10 +12,10 @@ class NewsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: context.colors.background, - appBar: AppBar( - backgroundColor: context.colors.accent, - title: const Text('News'), + appBar: const TopAppBar( + label: 'News', ), + body: const NewsBody(), ); } } diff --git a/lib/presentation/features/news/ui/news_body.dart b/lib/presentation/features/news/ui/news_body.dart new file mode 100644 index 0000000..a96ee5f --- /dev/null +++ b/lib/presentation/features/news/ui/news_body.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/features/_playground/playground_screen.dart'; +import 'package:flutter_template/presentation/resources/app_ui_constants.dart'; + +class NewsBody extends StatelessWidget { + const NewsBody({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: AppUiConstants.defaultScreenHorizontalPadding, + child: Center( + child: PlaygroundScreenOpenerButton(), + ), + ); + } +} diff --git a/lib/presentation/features/profile/cubit/profile_cubit.dart b/lib/presentation/features/profile/cubit/profile_cubit.dart new file mode 100644 index 0000000..90c08f2 --- /dev/null +++ b/lib/presentation/features/profile/cubit/profile_cubit.dart @@ -0,0 +1,50 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_template/domain/common/base_status/base_status.dart'; +import 'package:flutter_template/domain/common/response_error/response_error.dart'; +import 'package:flutter_template/domain/use_cases/profile/get_profile_use_case.dart'; +import 'package:flutter_template/presentation/features/profile/cubit/profile_state.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +final class ProfileCubit extends Cubit { + ProfileCubit(this._getProfileUseCase) : super(ProfileState.initial()); + + final GetProfileUseCase _getProfileUseCase; + + Future init() async { + if (state.initializationStatus.isLoading) { + return; + } + + emit( + state.copyWith(initializationStatus: const BaseStatus.loading()), + ); + + try { + final profileName = await _getProfileUseCase(); + + if (isClosed) { + return; + } + + return emit( + state.copyWith( + initializationStatus: const BaseStatus.success(), + name: profileName.fullName, + ), + ); + } catch (e) { + final responseError = ResponseError.from(e); + + if (isClosed) { + return; + } + + return emit( + state.copyWith( + initializationStatus: BaseStatus.failure(responseError), + ), + ); + } + } +} diff --git a/lib/presentation/feature/profile/profile_state.dart b/lib/presentation/features/profile/cubit/profile_state.dart similarity index 56% rename from lib/presentation/feature/profile/profile_state.dart rename to lib/presentation/features/profile/cubit/profile_state.dart index 6900e78..63908b3 100644 --- a/lib/presentation/feature/profile/profile_state.dart +++ b/lib/presentation/features/profile/cubit/profile_state.dart @@ -1,3 +1,4 @@ +import 'package:flutter_template/domain/common/base_status/base_status.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'profile_state.freezed.dart'; @@ -7,14 +8,11 @@ class ProfileState with _$ProfileState { const ProfileState._(); factory ProfileState({ - required bool isLoading, - required String name, + @Default(BaseStatus.initial()) BaseStatus initializationStatus, + @Default('N/A') String name, }) = _ProfileState; factory ProfileState.initial() { - return ProfileState( - isLoading: false, - name: '', - ); + return ProfileState(); } } diff --git a/lib/presentation/features/profile/profile_page.dart b/lib/presentation/features/profile/profile_page.dart new file mode 100644 index 0000000..0f4a857 --- /dev/null +++ b/lib/presentation/features/profile/profile_page.dart @@ -0,0 +1,27 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_template/injection/injector.dart'; +import 'package:flutter_template/presentation/features/profile/cubit/profile_cubit.dart'; +import 'package:flutter_template/presentation/features/profile/ui/profile_body.dart'; +import 'package:flutter_template/presentation/resources/resources.dart'; +import 'package:flutter_template/presentation/widgets/app_bar/top_app_bar.dart'; + +@RoutePage() +class ProfilePage extends StatelessWidget { + const ProfilePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => injector()..init(), + child: Scaffold( + backgroundColor: context.colors.background, + appBar: const TopAppBar( + label: 'Profile', + ), + body: const ProfileBody(), + ), + ); + } +} diff --git a/lib/presentation/features/profile/ui/profile_body.dart b/lib/presentation/features/profile/ui/profile_body.dart new file mode 100644 index 0000000..1c2ad52 --- /dev/null +++ b/lib/presentation/features/profile/ui/profile_body.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_template/presentation/features/profile/cubit/profile_cubit.dart'; +import 'package:flutter_template/presentation/widgets/button/app_button.dart'; +import 'package:flutter_template/presentation/widgets/loading_indicator/app_loading_indicator.dart'; +import 'package:flutter_template/presentation/widgets/text/app_text.dart'; + +class ProfileBody extends StatelessWidget { + const ProfileBody({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final profileCubit = context.read(); + + final status = context.select( + (ProfileCubit cubit) => cubit.state.initializationStatus, + ); + + final name = context.select( + (ProfileCubit cubit) => cubit.state.name, + ); + + final child = Column( + children: [ + Padding( + padding: const EdgeInsets.all(32.0), + child: status.maybeWhen( + loading: AppLoadingIndicator.small, + success: () => AppText.body( + name, + textAlign: TextAlign.center, + ), + orElse: () { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AppText.body( + 'Something went wrong.', + ), + AppButton.text( + label: 'Try again', + isSmall: true, + onPressed: profileCubit.init, + ), + ], + ); + }, + ), + ), + ], + ); + + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: child, + ); + } +} diff --git a/lib/presentation/feature/splash/splash_screen.dart b/lib/presentation/features/splash/splash_screen.dart similarity index 71% rename from lib/presentation/feature/splash/splash_screen.dart rename to lib/presentation/features/splash/splash_screen.dart index 9204c55..37d863e 100644 --- a/lib/presentation/feature/splash/splash_screen.dart +++ b/lib/presentation/features/splash/splash_screen.dart @@ -4,6 +4,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_template/presentation/resources/resources.dart'; import 'package:flutter_template/presentation/routes/router.gr.dart'; +import 'package:flutter_template/presentation/widgets/text/app_text.dart'; @RoutePage() class SplashScreen extends StatefulWidget { @@ -18,10 +19,10 @@ class _SplashScreenState extends State { @override void initState() { + super.initState(); _timer = Timer(const Duration(seconds: 2), () { - context.router.navigate(HomeRoute()); + context.router.replace(const MainRoute()); }); - super.initState(); } @override @@ -33,10 +34,13 @@ class _SplashScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: context.colors.accent, - body: const SafeArea( + backgroundColor: context.colors.appBarBackground, + body: SafeArea( child: Center( - child: Text('SplashScreen'), + child: AppText.body( + 'SplashScreen', + color: context.colors.foregroundOnAppBar, + ), ), ), ); diff --git a/lib/presentation/resources/app_color_palette.dart b/lib/presentation/resources/app_color_palette.dart new file mode 100644 index 0000000..8230b70 --- /dev/null +++ b/lib/presentation/resources/app_color_palette.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +/// Defines color palette for the application. +/// +/// Consider using the color name that is mentioned in Figma. +abstract final class AppColorPalette { + const AppColorPalette._(); + + static const alpha = Colors.transparent; + + static const americanOrange = Color(0xFFFF8800); + static const black = Color(0xFF000000); + static const blackLead = Color(0xFF202020); + static const cantaloupe = Color(0xFFFFE600); + static const darkGrey = Color(0xFF171717); + static const foggyGrey = Color(0xFF363636); + static const mutedGrey = Color(0xFF444444); + static const graphite = Color(0xFF444444); + static const keylime = Color(0xFFF0E68C); + static const lightLime = Color(0xFFF9F3C2); + static const lightSkyBlue = Color(0xFF4C9DFF); + static const oceanBlue = Color(0xFF1976D2); + static const red = Color(0xFFDD2C00); + static const silverPolish = Color(0xFFC6C6C6); + static const royalPurple = Color(0xFF4444DA); + static const ultraviolet = Color(0xFFB2AEF3); + static const white = Color(0xFFFFFFFF); + static const whisperingBlue = Color(0xFFD4E4FC); +} diff --git a/lib/presentation/resources/app_colors.dart b/lib/presentation/resources/app_colors.dart index 2cef8b9..9074b16 100644 --- a/lib/presentation/resources/app_colors.dart +++ b/lib/presentation/resources/app_colors.dart @@ -1,43 +1,133 @@ import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/app_color_palette.dart'; +import 'package:theme_tailor_annotation/theme_tailor_annotation.dart'; -class AppColors { - final Color background; - final Color surface; - final Color accent; - final Color text; +part 'app_colors.tailor.dart'; + +@TailorMixin() +class AppColors extends ThemeExtension with _$AppColorsTailorMixin { + static Color get black => AppColorPalette.black; + static Color get white => AppColorPalette.white; const AppColors({ + required this.primary, + required this.primaryVariant, + required this.secondary, + required this.secondaryVariant, required this.background, - required this.surface, - required this.accent, - required this.text, + required this.appBarBackground, + required this.danger, + required this.foregroundOnBackground, + required this.foregroundLightOnBackground, + required this.foregroundOnPrimary, + required this.foregroundOnSecondary, + required this.foregroundOnAppBar, + required this.foregroundOnDanger, + required this.outline, + required this.splashColor, + required this.disabledColor, + required this.transparant, }); -} -const colorsLight = AppColors( - background: Colors.white, - surface: Colors.white, - accent: Colors.blueAccent, - text: Colors.black, -); + // Core colors + @override + final Color primary; + @override + final Color primaryVariant; + @override + final Color secondary; + @override + final Color secondaryVariant; + @override + final Color background; + @override + final Color appBarBackground; + @override + final Color danger; + @override + final Color foregroundOnBackground; + @override + final Color foregroundLightOnBackground; + @override + final Color foregroundOnPrimary; + @override + final Color foregroundOnSecondary; + @override + final Color foregroundOnAppBar; + @override + final Color foregroundOnDanger; + @override + final Color outline; + @override + final Color transparant; + + // Other colors + @override + final Color splashColor; + @override + final Color disabledColor; + + factory AppColors.fromBrightness(Brightness brightness) => + switch (brightness) { + Brightness.light => AppColors.light(), + Brightness.dark => AppColors.dark(), + }; -const colorsDark = AppColors( - background: Colors.black54, - surface: Color(0xFF222222), - accent: Colors.lightBlueAccent, - text: Colors.white, -); + factory AppColors.light() { + return const AppColors( + primary: AppColorPalette.oceanBlue, + primaryVariant: AppColorPalette.lightSkyBlue, + secondary: AppColorPalette.ultraviolet, + secondaryVariant: AppColorPalette.lightLime, + background: AppColorPalette.white, + appBarBackground: AppColorPalette.oceanBlue, + danger: AppColorPalette.red, + foregroundOnBackground: AppColorPalette.black, + foregroundLightOnBackground: AppColorPalette.graphite, + foregroundOnPrimary: AppColorPalette.white, + foregroundOnSecondary: AppColorPalette.royalPurple, + foregroundOnAppBar: AppColorPalette.white, + foregroundOnDanger: AppColorPalette.white, + outline: AppColorPalette.oceanBlue, + transparant: AppColorPalette.alpha, + splashColor: AppColorPalette.whisperingBlue, + disabledColor: AppColorPalette.mutedGrey, + ); + } + + factory AppColors.dark() { + return const AppColors( + primary: AppColorPalette.americanOrange, + primaryVariant: AppColorPalette.cantaloupe, + secondary: AppColorPalette.keylime, + secondaryVariant: AppColorPalette.lightLime, + background: AppColorPalette.blackLead, + appBarBackground: AppColorPalette.darkGrey, + danger: AppColorPalette.red, + foregroundOnBackground: AppColorPalette.white, + foregroundLightOnBackground: AppColorPalette.foggyGrey, + foregroundOnPrimary: AppColorPalette.black, + foregroundOnSecondary: AppColorPalette.black, + foregroundOnAppBar: AppColorPalette.white, + foregroundOnDanger: AppColorPalette.white, + outline: AppColorPalette.americanOrange, + transparant: AppColorPalette.alpha, + splashColor: AppColorPalette.darkGrey, + disabledColor: AppColorPalette.silverPolish, + ); + } +} extension AppColorsExtension on BuildContext { AppColors get colors { - final brightness = Theme.of(this).brightness; - switch (brightness) { - case Brightness.light: - return colorsLight; - case Brightness.dark: - return colorsDark; - default: - return colorsLight; + final appColors = Theme.of(this).extension(); + + if (appColors == null) { + throw Exception( + 'Could not find the ThemeData extension for colors.\n Make sure to pass AppColors as ThemeData extension.', + ); } + + return appColors; } } diff --git a/lib/presentation/resources/app_colors.tailor.dart b/lib/presentation/resources/app_colors.tailor.dart new file mode 100644 index 0000000..30b827f --- /dev/null +++ b/lib/presentation/resources/app_colors.tailor.dart @@ -0,0 +1,188 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element, unnecessary_cast + +part of 'app_colors.dart'; + +// ************************************************************************** +// TailorAnnotationsGenerator +// ************************************************************************** + +mixin _$AppColorsTailorMixin on ThemeExtension { + Color get primary; + Color get primaryVariant; + Color get secondary; + Color get secondaryVariant; + Color get background; + Color get appBarBackground; + Color get danger; + Color get foregroundOnBackground; + Color get foregroundLightOnBackground; + Color get foregroundOnPrimary; + Color get foregroundOnSecondary; + Color get foregroundOnAppBar; + Color get foregroundOnDanger; + Color get outline; + Color get transparant; + Color get splashColor; + Color get disabledColor; + + @override + AppColors copyWith({ + Color? primary, + Color? primaryVariant, + Color? secondary, + Color? secondaryVariant, + Color? background, + Color? appBarBackground, + Color? danger, + Color? foregroundOnBackground, + Color? foregroundLightOnBackground, + Color? foregroundOnPrimary, + Color? foregroundOnSecondary, + Color? foregroundOnAppBar, + Color? foregroundOnDanger, + Color? outline, + Color? transparant, + Color? splashColor, + Color? disabledColor, + }) { + return AppColors( + primary: primary ?? this.primary, + primaryVariant: primaryVariant ?? this.primaryVariant, + secondary: secondary ?? this.secondary, + secondaryVariant: secondaryVariant ?? this.secondaryVariant, + background: background ?? this.background, + appBarBackground: appBarBackground ?? this.appBarBackground, + danger: danger ?? this.danger, + foregroundOnBackground: + foregroundOnBackground ?? this.foregroundOnBackground, + foregroundLightOnBackground: + foregroundLightOnBackground ?? this.foregroundLightOnBackground, + foregroundOnPrimary: foregroundOnPrimary ?? this.foregroundOnPrimary, + foregroundOnSecondary: + foregroundOnSecondary ?? this.foregroundOnSecondary, + foregroundOnAppBar: foregroundOnAppBar ?? this.foregroundOnAppBar, + foregroundOnDanger: foregroundOnDanger ?? this.foregroundOnDanger, + outline: outline ?? this.outline, + transparant: transparant ?? this.transparant, + splashColor: splashColor ?? this.splashColor, + disabledColor: disabledColor ?? this.disabledColor, + ); + } + + @override + AppColors lerp(covariant ThemeExtension? other, double t) { + if (other is! AppColors) return this as AppColors; + return AppColors( + primary: Color.lerp(primary, other.primary, t)!, + primaryVariant: Color.lerp(primaryVariant, other.primaryVariant, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + secondaryVariant: + Color.lerp(secondaryVariant, other.secondaryVariant, t)!, + background: Color.lerp(background, other.background, t)!, + appBarBackground: + Color.lerp(appBarBackground, other.appBarBackground, t)!, + danger: Color.lerp(danger, other.danger, t)!, + foregroundOnBackground: + Color.lerp(foregroundOnBackground, other.foregroundOnBackground, t)!, + foregroundLightOnBackground: Color.lerp( + foregroundLightOnBackground, other.foregroundLightOnBackground, t)!, + foregroundOnPrimary: + Color.lerp(foregroundOnPrimary, other.foregroundOnPrimary, t)!, + foregroundOnSecondary: + Color.lerp(foregroundOnSecondary, other.foregroundOnSecondary, t)!, + foregroundOnAppBar: + Color.lerp(foregroundOnAppBar, other.foregroundOnAppBar, t)!, + foregroundOnDanger: + Color.lerp(foregroundOnDanger, other.foregroundOnDanger, t)!, + outline: Color.lerp(outline, other.outline, t)!, + transparant: Color.lerp(transparant, other.transparant, t)!, + splashColor: Color.lerp(splashColor, other.splashColor, t)!, + disabledColor: Color.lerp(disabledColor, other.disabledColor, t)!, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is AppColors && + const DeepCollectionEquality().equals(primary, other.primary) && + const DeepCollectionEquality() + .equals(primaryVariant, other.primaryVariant) && + const DeepCollectionEquality().equals(secondary, other.secondary) && + const DeepCollectionEquality() + .equals(secondaryVariant, other.secondaryVariant) && + const DeepCollectionEquality() + .equals(background, other.background) && + const DeepCollectionEquality() + .equals(appBarBackground, other.appBarBackground) && + const DeepCollectionEquality().equals(danger, other.danger) && + const DeepCollectionEquality() + .equals(foregroundOnBackground, other.foregroundOnBackground) && + const DeepCollectionEquality().equals(foregroundLightOnBackground, + other.foregroundLightOnBackground) && + const DeepCollectionEquality() + .equals(foregroundOnPrimary, other.foregroundOnPrimary) && + const DeepCollectionEquality() + .equals(foregroundOnSecondary, other.foregroundOnSecondary) && + const DeepCollectionEquality() + .equals(foregroundOnAppBar, other.foregroundOnAppBar) && + const DeepCollectionEquality() + .equals(foregroundOnDanger, other.foregroundOnDanger) && + const DeepCollectionEquality().equals(outline, other.outline) && + const DeepCollectionEquality() + .equals(transparant, other.transparant) && + const DeepCollectionEquality() + .equals(splashColor, other.splashColor) && + const DeepCollectionEquality() + .equals(disabledColor, other.disabledColor)); + } + + @override + int get hashCode { + return Object.hash( + runtimeType.hashCode, + const DeepCollectionEquality().hash(primary), + const DeepCollectionEquality().hash(primaryVariant), + const DeepCollectionEquality().hash(secondary), + const DeepCollectionEquality().hash(secondaryVariant), + const DeepCollectionEquality().hash(background), + const DeepCollectionEquality().hash(appBarBackground), + const DeepCollectionEquality().hash(danger), + const DeepCollectionEquality().hash(foregroundOnBackground), + const DeepCollectionEquality().hash(foregroundLightOnBackground), + const DeepCollectionEquality().hash(foregroundOnPrimary), + const DeepCollectionEquality().hash(foregroundOnSecondary), + const DeepCollectionEquality().hash(foregroundOnAppBar), + const DeepCollectionEquality().hash(foregroundOnDanger), + const DeepCollectionEquality().hash(outline), + const DeepCollectionEquality().hash(transparant), + const DeepCollectionEquality().hash(splashColor), + const DeepCollectionEquality().hash(disabledColor), + ); + } +} + +extension AppColorsBuildContextProps on BuildContext { + AppColors get appColors => Theme.of(this).extension()!; + Color get primary => appColors.primary; + Color get primaryVariant => appColors.primaryVariant; + Color get secondary => appColors.secondary; + Color get secondaryVariant => appColors.secondaryVariant; + Color get background => appColors.background; + Color get appBarBackground => appColors.appBarBackground; + Color get danger => appColors.danger; + Color get foregroundOnBackground => appColors.foregroundOnBackground; + Color get foregroundLightOnBackground => + appColors.foregroundLightOnBackground; + Color get foregroundOnPrimary => appColors.foregroundOnPrimary; + Color get foregroundOnSecondary => appColors.foregroundOnSecondary; + Color get foregroundOnAppBar => appColors.foregroundOnAppBar; + Color get foregroundOnDanger => appColors.foregroundOnDanger; + Color get outline => appColors.outline; + Color get transparant => appColors.transparant; + Color get splashColor => appColors.splashColor; + Color get disabledColor => appColors.disabledColor; +} diff --git a/lib/presentation/resources/app_fonts.dart b/lib/presentation/resources/app_fonts.dart index c8b5d62..51299e5 100644 --- a/lib/presentation/resources/app_fonts.dart +++ b/lib/presentation/resources/app_fonts.dart @@ -1,3 +1,9 @@ -class AppFonts { - static const roboto = 'Roboto'; +import 'package:flutter_template/gen/fonts.gen.dart'; + +abstract final class AppFonts { + AppFonts._(); + + static const roboto = FontFamily.roboto; + + static String get activeFontFamily => roboto; } diff --git a/lib/presentation/resources/app_icons.dart b/lib/presentation/resources/app_icons.dart deleted file mode 100644 index ed8257c..0000000 --- a/lib/presentation/resources/app_icons.dart +++ /dev/null @@ -1,7 +0,0 @@ -class AppIcons { - AppIcons._private(); - - static const _iconsPath = 'assets/icons'; - - static const add = '$_iconsPath/add.svg'; -} diff --git a/lib/presentation/resources/app_text_styles.dart b/lib/presentation/resources/app_text_styles.dart index b5d1fb7..6032b47 100644 --- a/lib/presentation/resources/app_text_styles.dart +++ b/lib/presentation/resources/app_text_styles.dart @@ -1,12 +1,111 @@ import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/resources.dart'; +import 'package:theme_tailor_annotation/theme_tailor_annotation.dart'; -class AppTextStyles { - static const headline1 = TextStyle( - fontWeight: FontWeight.w300, - fontSize: 34, - ); - static const body1 = TextStyle( - fontWeight: FontWeight.w300, - fontSize: 14, - ); +part 'app_text_styles.tailor.dart'; + +@TailorMixin() +class AppTextStyles extends ThemeExtension + with _$AppTextStylesTailorMixin { + AppTextStyles({ + required this.header1, + required this.header2, + required this.header3, + required this.appBarTitle, + required this.bodySmall, + required this.body, + required this.bodyLarge, + required this.buttonLabel, + required this.underlineText, + }); + + factory AppTextStyles.fromBrightness(Brightness brightness) { + final _appColors = AppColors.fromBrightness(brightness); + + return AppTextStyles( + header1: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + fontFamily: AppFonts.activeFontFamily, + ), + header2: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + fontFamily: AppFonts.activeFontFamily, + ), + header3: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + fontFamily: AppFonts.activeFontFamily, + ), + appBarTitle: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: _appColors.foregroundOnAppBar, + fontFamily: AppFonts.activeFontFamily, + ), + bodySmall: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: _appColors.foregroundOnBackground, + fontFamily: AppFonts.activeFontFamily, + ), + body: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: _appColors.foregroundOnBackground, + fontFamily: AppFonts.activeFontFamily, + ), + bodyLarge: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: _appColors.foregroundOnBackground, + fontFamily: AppFonts.activeFontFamily, + ), + buttonLabel: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + fontFamily: AppFonts.activeFontFamily, + ), + underlineText: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: _appColors.foregroundOnBackground, + fontFamily: AppFonts.activeFontFamily, + decoration: TextDecoration.underline, + ), + ); + } + + @override + final TextStyle header1; + @override + final TextStyle header2; + @override + final TextStyle header3; + @override + final TextStyle appBarTitle; + @override + final TextStyle bodySmall; + @override + final TextStyle body; + @override + final TextStyle bodyLarge; + @override + final TextStyle buttonLabel; + @override + final TextStyle underlineText; +} + +extension AppTextStylesExtension on BuildContext { + AppTextStyles get textStyles { + final appTextStyles = Theme.of(this).extension(); + + if (appTextStyles == null) { + throw Exception( + 'Could not find the ThemeData extension for text styles.\n Make sure to pass AppTextStyles as ThemeData extension.', + ); + } + return appTextStyles; + } } diff --git a/lib/presentation/resources/app_text_styles.tailor.dart b/lib/presentation/resources/app_text_styles.tailor.dart new file mode 100644 index 0000000..f550cf4 --- /dev/null +++ b/lib/presentation/resources/app_text_styles.tailor.dart @@ -0,0 +1,110 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element, unnecessary_cast + +part of 'app_text_styles.dart'; + +// ************************************************************************** +// TailorAnnotationsGenerator +// ************************************************************************** + +mixin _$AppTextStylesTailorMixin on ThemeExtension { + TextStyle get header1; + TextStyle get header2; + TextStyle get header3; + TextStyle get appBarTitle; + TextStyle get bodySmall; + TextStyle get body; + TextStyle get bodyLarge; + TextStyle get buttonLabel; + TextStyle get underlineText; + + @override + AppTextStyles copyWith({ + TextStyle? header1, + TextStyle? header2, + TextStyle? header3, + TextStyle? appBarTitle, + TextStyle? bodySmall, + TextStyle? body, + TextStyle? bodyLarge, + TextStyle? buttonLabel, + TextStyle? underlineText, + }) { + return AppTextStyles( + header1: header1 ?? this.header1, + header2: header2 ?? this.header2, + header3: header3 ?? this.header3, + appBarTitle: appBarTitle ?? this.appBarTitle, + bodySmall: bodySmall ?? this.bodySmall, + body: body ?? this.body, + bodyLarge: bodyLarge ?? this.bodyLarge, + buttonLabel: buttonLabel ?? this.buttonLabel, + underlineText: underlineText ?? this.underlineText, + ); + } + + @override + AppTextStyles lerp(covariant ThemeExtension? other, double t) { + if (other is! AppTextStyles) return this as AppTextStyles; + return AppTextStyles( + header1: TextStyle.lerp(header1, other.header1, t)!, + header2: TextStyle.lerp(header2, other.header2, t)!, + header3: TextStyle.lerp(header3, other.header3, t)!, + appBarTitle: TextStyle.lerp(appBarTitle, other.appBarTitle, t)!, + bodySmall: TextStyle.lerp(bodySmall, other.bodySmall, t)!, + body: TextStyle.lerp(body, other.body, t)!, + bodyLarge: TextStyle.lerp(bodyLarge, other.bodyLarge, t)!, + buttonLabel: TextStyle.lerp(buttonLabel, other.buttonLabel, t)!, + underlineText: TextStyle.lerp(underlineText, other.underlineText, t)!, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is AppTextStyles && + const DeepCollectionEquality().equals(header1, other.header1) && + const DeepCollectionEquality().equals(header2, other.header2) && + const DeepCollectionEquality().equals(header3, other.header3) && + const DeepCollectionEquality() + .equals(appBarTitle, other.appBarTitle) && + const DeepCollectionEquality().equals(bodySmall, other.bodySmall) && + const DeepCollectionEquality().equals(body, other.body) && + const DeepCollectionEquality().equals(bodyLarge, other.bodyLarge) && + const DeepCollectionEquality() + .equals(buttonLabel, other.buttonLabel) && + const DeepCollectionEquality() + .equals(underlineText, other.underlineText)); + } + + @override + int get hashCode { + return Object.hash( + runtimeType.hashCode, + const DeepCollectionEquality().hash(header1), + const DeepCollectionEquality().hash(header2), + const DeepCollectionEquality().hash(header3), + const DeepCollectionEquality().hash(appBarTitle), + const DeepCollectionEquality().hash(bodySmall), + const DeepCollectionEquality().hash(body), + const DeepCollectionEquality().hash(bodyLarge), + const DeepCollectionEquality().hash(buttonLabel), + const DeepCollectionEquality().hash(underlineText), + ); + } +} + +extension AppTextStylesBuildContextProps on BuildContext { + AppTextStyles get appTextStyles => Theme.of(this).extension()!; + TextStyle get header1 => appTextStyles.header1; + TextStyle get header2 => appTextStyles.header2; + TextStyle get header3 => appTextStyles.header3; + TextStyle get appBarTitle => appTextStyles.appBarTitle; + TextStyle get bodySmall => appTextStyles.bodySmall; + TextStyle get body => appTextStyles.body; + TextStyle get bodyLarge => appTextStyles.bodyLarge; + TextStyle get buttonLabel => appTextStyles.buttonLabel; + TextStyle get underlineText => appTextStyles.underlineText; +} diff --git a/lib/presentation/resources/app_theme.dart b/lib/presentation/resources/app_theme.dart index 0aec87e..3744b8d 100644 --- a/lib/presentation/resources/app_theme.dart +++ b/lib/presentation/resources/app_theme.dart @@ -1,12 +1,62 @@ import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/extensions/color_extensions.dart'; import 'package:flutter_template/presentation/resources/resources.dart'; -ThemeData getAppTheme(Brightness brightness) { - final colors = brightness == Brightness.light ? colorsLight : colorsDark; +abstract final class AppTheme { + const AppTheme._(); - return ThemeData( - brightness: brightness, - fontFamily: AppFonts.roboto, - scaffoldBackgroundColor: colors.background, - ); + static ThemeData fromBrightness(Brightness brightness) { + final _appColors = AppColors.fromBrightness(brightness); + final _appTextStyles = AppTextStyles.fromBrightness(brightness); + + final _lightColorScheme = ColorScheme.light( + brightness: brightness, + primary: _appColors.primary, + onPrimary: _appColors.foregroundOnPrimary, + surface: _appColors.primary, + secondary: _appColors.secondary, + onSecondary: _appColors.foregroundOnSecondary, + onSurface: _appColors.foregroundOnBackground, + outline: _appColors.outline, + error: _appColors.danger, + onError: _appColors.foregroundOnDanger, + ); + + final _darkColorScheme = ColorScheme.dark( + brightness: brightness, + primary: _appColors.primary, + onPrimary: _appColors.foregroundOnPrimary, + secondary: _appColors.secondary, + onSecondary: _appColors.foregroundOnSecondary, + surface: _appColors.background, + onSurface: _appColors.foregroundOnBackground, + outline: _appColors.outline, + error: _appColors.danger, + onError: _appColors.foregroundOnDanger, + ); + + final _appColorScheme = switch (brightness) { + Brightness.light => _lightColorScheme, + Brightness.dark => _darkColorScheme, + }; + + return ThemeData( + brightness: brightness, + scaffoldBackgroundColor: _appColors.background, + splashFactory: InkRipple.splashFactory, + fontFamily: AppFonts.activeFontFamily, + colorScheme: _appColorScheme, + highlightColor: Colors.transparent, + splashColor: _appColors.splashColor, + disabledColor: _appColors.primary.lowOpacity(), + appBarTheme: AppBarTheme( + color: _appColors.appBarBackground, + foregroundColor: _appColors.foregroundOnAppBar, + ), + extensions: [ + _appColors, + _appTextStyles, + ], + ); + } } diff --git a/lib/presentation/resources/app_ui_constants.dart b/lib/presentation/resources/app_ui_constants.dart new file mode 100644 index 0000000..02032a7 --- /dev/null +++ b/lib/presentation/resources/app_ui_constants.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +/// Represents a collection of constants specific to the app's user interface (UI). +/// +/// This class contains non-sensitive information and is designed to maintain consistency +/// across the app's UI elements. +abstract final class AppUiConstants { + AppUiConstants._(); + + // Animations + static const animationDuration = Duration(milliseconds: 250); + + // Curves + static const transitionCurve = Curves.fastEaseInToSlowEaseOut; + + // Paddings + static const defaultScreenHorizontalPadding = + EdgeInsets.symmetric(horizontal: 24.0); + + static const defaultSmallButtonContentPadding = EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ); + + static const defaultButtonContentPadding = EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 18.0, + ); + + // Text styles + static const defaultSmallButtonTextStyle = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ); + + static const defaultButtonTextStyle = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ); + + // Border radius + /// Assign border radius value if you want to have radius on buttons, textfields, etc. + /// Don't assign any value if you want to have the default full rounded radius (Stadium border). + /// Example — + /// ```dart + /// static double? defaultBorderRadius; + /// ```` + static double? defaultBorderRadius = 12.0; +} diff --git a/lib/presentation/resources/resources.dart b/lib/presentation/resources/resources.dart index 1c9be79..1e613a4 100644 --- a/lib/presentation/resources/resources.dart +++ b/lib/presentation/resources/resources.dart @@ -1,6 +1,5 @@ export 'package:flutter_template/presentation/resources/app_colors.dart'; -export 'package:flutter_template/presentation/resources/app_icons.dart'; +export 'package:flutter_template/presentation/resources/app_fonts.dart'; export 'package:flutter_template/presentation/resources/app_images.dart'; export 'package:flutter_template/presentation/resources/app_text_styles.dart'; -export 'package:flutter_template/presentation/resources/app_fonts.dart'; export 'package:flutter_template/presentation/resources/app_theme.dart'; diff --git a/lib/presentation/routes/router.dart b/lib/presentation/routes/router.dart index 737714d..15a0e41 100644 --- a/lib/presentation/routes/router.dart +++ b/lib/presentation/routes/router.dart @@ -1,8 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_template/presentation/routes/router.gr.dart'; +/// 💡 Tip: Both `~Screen`, `~Page` will be treated as `~Route` by [AutoRoute]. +/// +/// Consider using `~Screen` as screen naming, and `~Page` if it is part of screen view, +/// for example, Bottom navigation child views, tab views, etc. +/// @AutoRouterConfig() -class AppRouter extends $AppRouter { +class AppRouter extends RootStackRouter { @override RouteType get defaultRouteType => const RouteType.material(); @@ -11,10 +16,33 @@ class AppRouter extends $AppRouter { AutoRoute( page: SplashRoute.page, path: '/', + initial: true, ), AutoRoute( - page: HomeRoute.page, - path: '/home', + page: MainRoute.page, + path: '/main', + children: [ + AutoRoute( + page: HomeRoute.page, + path: 'news', + initial: true, + ), + AutoRoute( + page: NewsRoute.page, + path: 'news', + ), + AutoRoute( + page: ProfileRoute.page, + path: 'profile', + ), + ], + ), + + // Playground Screen: Keep it as the last item of this list. + AutoRoute( + page: PlaygroundRoute.page, + path: '/playground', + fullscreenDialog: true, ), ]; } diff --git a/lib/presentation/utils/not_implemented_dialog/not_implemented_dialog.dart b/lib/presentation/utils/not_implemented_dialog/not_implemented_dialog.dart index 7bab9cb..b59df35 100644 --- a/lib/presentation/utils/not_implemented_dialog/not_implemented_dialog.dart +++ b/lib/presentation/utils/not_implemented_dialog/not_implemented_dialog.dart @@ -29,7 +29,7 @@ class NotImplementedDialog extends StatelessWidget { TextButton( onPressed: Navigator.of(context).pop, child: const Text('OK'), - ) + ), ], ); } diff --git a/lib/presentation/widgets/app_bar/top_app_bar.dart b/lib/presentation/widgets/app_bar/top_app_bar.dart new file mode 100644 index 0000000..9939ac6 --- /dev/null +++ b/lib/presentation/widgets/app_bar/top_app_bar.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/app_colors.dart'; +import 'package:flutter_template/presentation/widgets/text/app_text.dart'; +import 'package:flutter_template/presentation/widgets/utilities/top_app_bar_config.dart'; + +// TODO: Replace the name with Apps-specific naming (XyzAppBar) +class TopAppBar extends StatelessWidget implements PreferredSizeWidget { + const TopAppBar({ + super.key, + this.label, + this.actions, + this.automaticallyImplyLeading = true, + this.centerTitle = true, + }); + + final String? label; + final List? actions; + final bool automaticallyImplyLeading; + final bool centerTitle; + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + return TopAppBarConfig( + child: AppBar( + title: label == null + ? null + : AppText.appBarTitle( + label!, + color: context.colors.foregroundOnAppBar, + ), + actions: actions, + automaticallyImplyLeading: automaticallyImplyLeading, + centerTitle: centerTitle, + surfaceTintColor: Colors.transparent, + ), + ); + } +} diff --git a/lib/presentation/widgets/button/app_button.dart b/lib/presentation/widgets/button/app_button.dart new file mode 100644 index 0000000..8fa5c26 --- /dev/null +++ b/lib/presentation/widgets/button/app_button.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/extensions/color_extensions.dart'; +import 'package:flutter_template/presentation/resources/app_colors.dart'; +import 'package:flutter_template/presentation/resources/app_ui_constants.dart'; +import 'package:flutter_template/presentation/widgets/loading_indicator/app_loading_indicator.dart'; +import 'package:flutter_template/presentation/widgets/text/app_text.dart'; + +enum _AppButtonType { + primary, + secondary, + outlined, + text, + danger, +} + +class AppButton extends StatelessWidget { + const AppButton._({ + Key? key, + required this.buttonType, + required this.label, + this.onPressed, + this.isLoading = false, + this.isSmall = false, + this.isDisabled = false, + }) : super(key: key); + + factory AppButton.primary({ + required String label, + VoidCallback? onPressed, + bool isLoading = false, + bool isSmall = false, + bool isDisabled = false, + }) { + return AppButton._( + buttonType: _AppButtonType.primary, + label: label, + isLoading: isLoading, + isSmall: isSmall, + isDisabled: isDisabled, + onPressed: onPressed, + ); + } + + factory AppButton.secondary({ + required String label, + VoidCallback? onPressed, + bool isLoading = false, + bool isSmall = false, + bool isDisabled = false, + }) { + return AppButton._( + buttonType: _AppButtonType.secondary, + label: label, + isLoading: isLoading, + isSmall: isSmall, + isDisabled: isDisabled, + onPressed: onPressed, + ); + } + + factory AppButton.outlined({ + required String label, + VoidCallback? onPressed, + bool isLoading = false, + bool isSmall = false, + bool isDisabled = false, + }) { + return AppButton._( + buttonType: _AppButtonType.outlined, + label: label, + isLoading: isLoading, + isSmall: isSmall, + isDisabled: isDisabled, + onPressed: onPressed, + ); + } + + factory AppButton.text({ + required String label, + VoidCallback? onPressed, + bool isLoading = false, + bool isSmall = false, + bool isDisabled = false, + }) { + return AppButton._( + buttonType: _AppButtonType.text, + label: label, + isLoading: isLoading, + isSmall: isSmall, + isDisabled: isDisabled, + onPressed: onPressed, + ); + } + + factory AppButton.destructive({ + required String label, + VoidCallback? onPressed, + bool isLoading = false, + bool isSmall = false, + bool isDisabled = false, + }) { + return AppButton._( + buttonType: _AppButtonType.danger, + label: label, + isLoading: isLoading, + isSmall: isSmall, + isDisabled: isDisabled, + onPressed: onPressed, + ); + } + + final _AppButtonType buttonType; + final String label; + final VoidCallback? onPressed; + final bool isLoading; + final bool isSmall; + final bool isDisabled; + + @override + Widget build(BuildContext context) { + final borderRadius = AppUiConstants.defaultBorderRadius; + + final _colors = context.colors; + + final _padding = isSmall + ? AppUiConstants.defaultSmallButtonContentPadding + : AppUiConstants.defaultButtonContentPadding; + + final _textStyle = isSmall + ? AppUiConstants.defaultSmallButtonTextStyle + : AppUiConstants.defaultButtonTextStyle; + + final shape = borderRadius == null + ? null + : WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + AppUiConstants.defaultBorderRadius ?? .0, + ), + ), + ); + + final disableOnPressed = isLoading || isDisabled; + + return SizedBox( + width: double.infinity, + child: switch (buttonType) { + _AppButtonType.primary => TextButton( + style: TextButton.styleFrom( + padding: _padding, + textStyle: _textStyle, + backgroundColor: + isDisabled ? _colors.primary.lowOpacity() : _colors.primary, + foregroundColor: _colors.foregroundOnPrimary, + ).copyWith( + shape: shape, + ), + onPressed: disableOnPressed ? null : onPressed, + child: isLoading + ? AppLoadingIndicator.small( + indicatorColor: _colors.foregroundOnPrimary, + ) + : _AppButtonLabel(label), + ), + _AppButtonType.secondary => TextButton( + style: TextButton.styleFrom( + padding: _padding, + textStyle: _textStyle, + backgroundColor: isDisabled + ? _colors.secondary.lowOpacity() + : _colors.secondary, + foregroundColor: _colors.foregroundOnSecondary, + ).copyWith( + shape: shape, + ), + onPressed: disableOnPressed ? null : onPressed, + child: isLoading + ? AppLoadingIndicator.small( + indicatorColor: _colors.foregroundOnSecondary, + ) + : _AppButtonLabel(label), + ), + _AppButtonType.outlined => OutlinedButton( + style: OutlinedButton.styleFrom( + padding: _padding, + textStyle: _textStyle, + ).copyWith( + shape: shape, + ), + onPressed: disableOnPressed ? null : onPressed, + child: isLoading + ? AppLoadingIndicator.small( + indicatorColor: _colors.foregroundOnBackground, + ) + : _AppButtonLabel(label), + ), + _AppButtonType.text => TextButton( + style: TextButton.styleFrom( + padding: _padding, + textStyle: _textStyle, + ).copyWith( + shape: shape, + ), + onPressed: disableOnPressed ? null : onPressed, + child: isLoading + ? AppLoadingIndicator.small( + indicatorColor: _colors.foregroundOnBackground, + ) + : _AppButtonLabel(label), + ), + _AppButtonType.danger => TextButton( + style: TextButton.styleFrom( + padding: _padding, + textStyle: _textStyle, + backgroundColor: + isDisabled ? _colors.danger.lowOpacity() : _colors.danger, + foregroundColor: _colors.foregroundOnDanger, + ).copyWith( + shape: shape, + ), + onPressed: disableOnPressed ? null : onPressed, + child: isLoading + ? AppLoadingIndicator.small( + indicatorColor: _colors.foregroundOnDanger, + ) + : _AppButtonLabel(label), + ), + }, + ); + } +} + +class _AppButtonLabel extends StatelessWidget { + const _AppButtonLabel(this.label); + + final String label; + + @override + Widget build(BuildContext context) { + return AppText.buttonLabel( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ); + } +} diff --git a/lib/presentation/widgets/disable_widget/app_disable_widget.dart b/lib/presentation/widgets/disable_widget/app_disable_widget.dart new file mode 100644 index 0000000..2ea1d53 --- /dev/null +++ b/lib/presentation/widgets/disable_widget/app_disable_widget.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/app_ui_constants.dart'; +import 'package:flutter_template/presentation/resources/resources.dart'; + +class AppDisableWidget extends StatelessWidget { + const AppDisableWidget({ + super.key, + this.disable = true, + this.applyBlackWhiteFilter = true, + this.opacity = 0.66, + this.animate = false, + required this.child, + }); + + final bool disable; + final double opacity; + final bool applyBlackWhiteFilter; + final bool animate; + final Widget child; + + @override + Widget build(BuildContext context) { + Widget widget = IgnorePointer( + ignoring: disable, + child: child, + ); + + if (applyBlackWhiteFilter) { + widget = ColorFiltered( + colorFilter: ColorFilter.mode( + disable ? context.colors.background : Colors.transparent, + disable ? BlendMode.saturation : BlendMode.overlay, + ), + child: widget, + ); + } + + final opacity = disable ? this.opacity : 1.0; + + return animate + ? AnimatedOpacity( + opacity: opacity, + duration: AppUiConstants.animationDuration, + curve: Curves.fastEaseInToSlowEaseOut, + child: widget, + ) + : Opacity( + opacity: opacity, + child: widget, + ); + } +} diff --git a/lib/presentation/widgets/loading_indicator/app_loading_indicator.dart b/lib/presentation/widgets/loading_indicator/app_loading_indicator.dart new file mode 100644 index 0000000..fec8a98 --- /dev/null +++ b/lib/presentation/widgets/loading_indicator/app_loading_indicator.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class AppLoadingIndicator extends StatelessWidget { + /// Determines both height and width of the [AppLoadingIndicator] + static const defaultSize = 24.0; + + /// Determines both height and width of the [AppLoadingIndicator.small] + static const defaultSmallSize = 16.0; + + /// Determines the color of the loading indicator. + /// + /// By default, It will follow the primary color of the current theme. + final Color? indicatorColor; + + const AppLoadingIndicator({ + super.key, + this.size = defaultSize, + this.strokeWidth = 4.0, + this.indicatorColor, + }); + + /// Determines both height and width of the loader. + final double size; + + /// The width of the material loader stroke. + final double strokeWidth; + + factory AppLoadingIndicator.small({ + Color? indicatorColor, + }) { + return AppLoadingIndicator( + size: defaultSmallSize, + strokeWidth: 2.0, + indicatorColor: indicatorColor, + ); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: size, + width: size, + child: CircularProgressIndicator.adaptive( + strokeWidth: strokeWidth, + backgroundColor: indicatorColor, + ), + ); + } +} diff --git a/lib/presentation/widgets/spacers/safe_area_spacer.dart b/lib/presentation/widgets/spacers/safe_area_spacer.dart new file mode 100644 index 0000000..e4bae8e --- /dev/null +++ b/lib/presentation/widgets/spacers/safe_area_spacer.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +/// Create safe area space. Useful for the beginning/end of the pages +/// to make sure the "padding" of it is above the safe area. +class SafeAreaSpacer extends StatelessWidget { + const SafeAreaSpacer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final padding = MediaQuery.maybePaddingOf(context); + + if (padding == null) { + return const SizedBox.shrink(); + } + + final insets = padding.bottom; + + return SizedBox(height: insets); + } +} diff --git a/lib/presentation/widgets/text/app_text.dart b/lib/presentation/widgets/text/app_text.dart new file mode 100644 index 0000000..ee26876 --- /dev/null +++ b/lib/presentation/widgets/text/app_text.dart @@ -0,0 +1,381 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/resources/resources.dart'; + +enum _AppTextType { + header1, + header2, + header3, + appBarTitle, + bodySmall, + body, + bodyLarge, + buttonLabel, + underlineText, +} + +class AppText extends StatelessWidget { + const AppText._( + this.text, { + Key? key, + required this.type, + this.color, + this.textAlign, + this.maxLines, + this.overflow, + this.letterSpacing, + this.fontFamily, + this.lineHeight, + this.fontWeight, + this.fontSize, + this.enableAutoTextSize = false, + }) : super(key: key); + + factory AppText.header1( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.header1, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.header2( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.header2, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.header3( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.header3, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.appBarTitle( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.appBarTitle, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.bodySmall( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.bodySmall, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.body( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.body, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.bodyLarge( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.bodyLarge, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.buttonLabel( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.buttonLabel, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.underlineText( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.underlineText, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + enableAutoTextSize: enableAutoTextSize, + ); + } + + factory AppText.custom( + String text, { + Key? key, + Color? color, + TextAlign? textAlign, + int? maxLines, + TextOverflow? overflow, + double? letterSpacing, + String? fontFamily, + double? lineHeight, + FontWeight? fontWeight, + double? fontSize, + bool enableAutoTextSize = false, + }) { + return AppText._( + text, + type: _AppTextType.body, + key: key, + color: color, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + letterSpacing: letterSpacing, + fontFamily: fontFamily, + lineHeight: lineHeight, + fontWeight: fontWeight, + fontSize: fontSize, + enableAutoTextSize: enableAutoTextSize, + ); + } + + final String text; + final _AppTextType type; + final Color? color; + final TextAlign? textAlign; + final int? maxLines; + final TextOverflow? overflow; + final double? letterSpacing; + final String? fontFamily; + final double? lineHeight; + final FontWeight? fontWeight; + final double? fontSize; + final bool enableAutoTextSize; + + @override + Widget build(BuildContext context) { + final _appTextStyles = context.textStyles; + + final textStyle = switch (type) { + _AppTextType.header1 => _appTextStyles.header1, + _AppTextType.header2 => _appTextStyles.header2, + _AppTextType.header3 => _appTextStyles.header3, + _AppTextType.appBarTitle => _appTextStyles.appBarTitle, + _AppTextType.bodySmall => _appTextStyles.bodySmall, + _AppTextType.body => _appTextStyles.body, + _AppTextType.bodyLarge => _appTextStyles.bodyLarge, + _AppTextType.buttonLabel => _appTextStyles.buttonLabel, + _AppTextType.underlineText => _appTextStyles.underlineText, + }; + + final customTextStyle = textStyle.copyWith( + color: color, + letterSpacing: letterSpacing, + fontSize: fontSize, + fontWeight: fontWeight, + ); + + if (enableAutoTextSize) { + return AutoSizeText( + text, + style: customTextStyle, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + minFontSize: 6, + ); + } + + return Text( + text, + style: customTextStyle, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + ); + } +} diff --git a/lib/presentation/widgets/utilities/focus_scope_dismissible.dart b/lib/presentation/widgets/utilities/app_screen_config.dart similarity index 53% rename from lib/presentation/widgets/utilities/focus_scope_dismissible.dart rename to lib/presentation/widgets/utilities/app_screen_config.dart index e3eab20..6d49dc5 100644 --- a/lib/presentation/widgets/utilities/focus_scope_dismissible.dart +++ b/lib/presentation/widgets/utilities/app_screen_config.dart @@ -1,34 +1,38 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -/// A widget for dismissing the keyboard on tap outside. +/// A configuration widget designed to manage actions when tapping on the scaffold, such as dismissing the keyboard. /// -/// Wrap a widget that contain text inputs with this widget -/// to enable tap-outside-to-dismiss-keyboard behaviour. +/// Wrap a widget containing text inputs with this widget to enable tap-outside-to-dismiss-keyboard behavior. /// -/// So instead of this: +/// Example Usage: +/// ```dart +/// AppScreenConfig( +/// child: MyScreen(), +/// ) +/// ``` /// +/// Instead of manually handling tap events to dismiss the keyboard: /// ```dart /// GestureDetector( /// onTap: () { -/// final focusScope = FocusScope.of(context); -/// if (focusScope.hasFocus) { -/// focusScope.unfocus(); -/// } -/// }, -/// child: MyPage(), +/// final focusScope = FocusScope.of(context); +/// if (focusScope.hasFocus) { +/// focusScope.unfocus(); +/// } +/// }, +/// child: MyScreen(), /// ), /// ``` /// -/// You could do this: -/// +/// You can simplify it using `AppScreenConfig`: /// ```dart -/// BackgroundFocusScopeDismisser( -/// child: MyPage(), +/// AppScreenConfig( +/// child: MyScreen(), /// ), /// ``` -class FocusScopeDismissible extends StatelessWidget { - const FocusScopeDismissible({ +class AppScreenConfig extends StatelessWidget { + const AppScreenConfig({ Key? key, this.excludeFromSemantics = false, this.dragStartBehavior = DragStartBehavior.start, diff --git a/lib/presentation/widgets/utilities/top_app_bar_config.dart b/lib/presentation/widgets/utilities/top_app_bar_config.dart new file mode 100644 index 0000000..bea834c --- /dev/null +++ b/lib/presentation/widgets/utilities/top_app_bar_config.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_template/presentation/extensions/scroll_controller_extensions.dart'; + +/// A configurable widget designed for defining actions triggered by tapping on the [Scaffold]'s top app bar. +/// +/// The primary use case for this widget is to facilitate scrolling to the top of the screen. +/// If the screen is already scrolled to the top, the widget will alternatively unfocus and +/// dismiss the keyboard if it is currently shown. +class TopAppBarConfig extends StatelessWidget { + const TopAppBarConfig({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + final controller = PrimaryScrollController.maybeOf(context); + + final hasClients = controller?.hasClients ?? false; + + if (!hasClients) { + FocusManager.instance.primaryFocus?.unfocus(); + return; + } + + if (controller?.position.pixels == 0.0) { + FocusManager.instance.primaryFocus?.unfocus(); + } + + controller?.animateToTop(); + }, + child: child, + ); + } +} diff --git a/mason-lock.json b/mason-lock.json deleted file mode 100644 index eab10ae..0000000 --- a/mason-lock.json +++ /dev/null @@ -1 +0,0 @@ -{"bricks":{}} \ No newline at end of file diff --git a/mason.yaml b/mason.yaml index 4f00993..83977aa 100644 --- a/mason.yaml +++ b/mason.yaml @@ -1 +1,21 @@ +# Register bricks which can be consumed via the Mason CLI. +# Run "mason get" to install all registered bricks. +# To learn more, visit https://docs.brickhub.dev. bricks: + # service brick + service: + git: + url: "https://github.com/monstar-lab-oss/flutter-bricks.git" + path: bricks/service + + # usecase brick + usecase: + git: + url: "https://github.com/monstar-lab-oss/flutter-bricks.git" + path: bricks/usecase + + # feature brick + feature: + git: + url: "https://github.com/monstar-lab-oss/flutter-bricks.git" + path: bricks/feature diff --git a/playbooks/_pictures/ScreenshotsBadExample.png b/playbooks/_pictures/ScreenshotsBadExample.png new file mode 100644 index 0000000..ebc69c4 Binary files /dev/null and b/playbooks/_pictures/ScreenshotsBadExample.png differ diff --git a/playbooks/_pictures/ScreenshotsGoodExample.png b/playbooks/_pictures/ScreenshotsGoodExample.png new file mode 100644 index 0000000..28a35ad Binary files /dev/null and b/playbooks/_pictures/ScreenshotsGoodExample.png differ diff --git a/playbooks/organization/WorkingWithNStack.md b/playbooks/organization/WorkingWithNStack.md new file mode 100644 index 0000000..920cbac --- /dev/null +++ b/playbooks/organization/WorkingWithNStack.md @@ -0,0 +1,165 @@ +# Working with NStack + +> [!IMPORTANT] +> ⚠️ **Notes regarding the NStack** +> +> NStack might currently be down as the +> organization plans to sunset this service. +> However, this guide will assist future +> developers if they choose to work with it & +> NStack is up again. + +_Please visit [Nstack website](https://www.nstack.io) to check if it's up._ + +The feature integration was removed in the [chore: Removed NStack feature integration](https://github.com/ml-opensource/flutter-template/pull/161) Pull Request. + +**NStack** is a Backend-as-a-Service (BaaS) developed by Nodes/Monstarlab. It offers features such as localization, in-app messaging, and rate limiting. + +--- + +## Getting Started + +For more details about the Flutter package for the NStack library, visit [NStack Flutter SDK on GitHub](https://github.com/nstack-io/flutter-sdk). + +--- + +## Installation + +To use NStack, you'll need a typical `build_runner` setup. Follow these steps to install the required packages by adding them to your `pubspec.yaml` file: + +```yaml +dependencies: + nstack: + git: + url: https://github.com/nstack-io/flutter-sdk.git + ref: v0.5.1 + +dev_dependencies: + build_runner: +``` + +### Additional Notes + +- **Circular Dependencies:** Some packages may cause circular dependency issues. To resolve this, specify exact versions of those dependencies. + + Example: + + ```yaml + flutter_svg: 2.0.9 + lottie: 3.0.0 + ``` + +--- + +## Running the Code Generator + +1. **Create Configuration File:** + Create a file named `nstack.json` under `/lib/nstack` with the following content: + + ```json + { + "version": 1, + "nstack_project_id": "YOUR_PROJECT_ID", + "nstack_api_key": "YOUR_REST_API_KEY" + } + ``` + +2. **Run the Generator:** + Based on your use case, run one of the following commands: + + - If your package depends on Flutter: + + ```bash + flutter pub run build_runner build + ``` + + - Otherwise: + + ```bash + pub run build_runner build + ``` + + A successful execution will generate a `nstack.dart` file tailored to your project. + +3. **Incremental Updates:** + To watch for changes in `nstack.json` and trigger automatic rebuilds, use the following command: + + ```bash + flutter pub run build_runner watch --delete-conflicting-outputs + ``` + + Increment the `"version"` number in `nstack.json` and save to trigger an update. + +--- + +## Example Usage + +1. **Import Generated File:** + Import the `nstack.dart` file in your project. + +2. **Integrate NStack Widget:** + In your `app.dart`, under `MaterialApp.router`, use the following code in the `builder`: + + ```dart + return MaterialApp.router( + // ... other codes go here + builder: (context, widget) { + if (widget == null) { + return const SizedBox(); + } + + return NStackWidget( + child: widget, + ); + }, + ); + ``` + +3. **Analysis Options:** + Add the following to your `analysis_options.yaml` to exclude the generated `nstack.dart` file: + + ```yaml + - "**/*nstack.dart" + ``` + +--- + +## Additional Setup + +### Java and Gradle Compatibility + +Ensure you have **Java 17** and a compatible Gradle version for Android builds. + +### Workaround for Namespace Issue + +If NStack complains about missing namespaces in its library, apply the following workaround in your `android/build.gradle`: + +```gradle +// android/build.gradle +rootProject.buildDir = "../build" // <-- existing line + +// Start of NStack Workaround +subprojects { + afterEvaluate { project -> + if (project.name == 'nstack') { + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + project.buildscript { + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20" + } + } + } + } + } +} +// End of NStack Workaround +``` + +--- + +This guide provides a complete overview of integrating and using NStack in a Flutter project. If you encounter issues, feel free to consult the official [NStack Flutter SDK documentation](https://github.com/nstack-io/flutter-sdk). diff --git a/playbooks/organization/WorkingWithPullRequests.md b/playbooks/organization/WorkingWithPullRequests.md new file mode 100644 index 0000000..a424fe2 --- /dev/null +++ b/playbooks/organization/WorkingWithPullRequests.md @@ -0,0 +1,139 @@ +# 🛠️ Working with Pull Requests (PR) + +## Code Review process + +When it comes to code review process, let's aim to make it efficient and convenient for both the reviewer and the author of the Pull Request. + +### Definition of Readiness + +A Pull Request is considered as ready for code review when: + +- All checkboxes in the pull request template are checked. +- A review is requested from a team member who ideally has sufficient knowledge about the specific topic, feature, or area. Feel free to involve multiple individuals in the review process. + +### When Requesting a Review + +1. **Preserve the PR Template:** First of all, do not remove the pull request template unless it's necessary. Stick to the current template by filling in the required details. + +2. **Consider the Reviewer's Perspective:** When you submit your code for review, imagine yourself on the other side of the PR. + +- Is your code ready to be reviewed? +- Are the needed details / clarifications provided in the description? +- Is the needed person's review requested? + +3. **Keep Discussions within GitHub:** Prefer keeping the context inside the PR. E.g. minimize discussions around the code and the solution outside of GitHub. + +Below you can find recommendations / requirements for the PR organization. These are must-to-follow rules and is applicable for every PR with a few exceptions. + +### When Reviewing a PR + +Let's not talk about obvious things like being polite, giving constructive feedback etc. As a reviewer: + +- Ensure that the PR is ready to be reviewed before starting reviewing. Read the description to understand whether all conditions are met and review the code. Otherwise, request the author to update the missing details. +- If a code is unclear and makes you confused, consider requesting documenting unclear parts of the code. Either in the code itself or maybe in a dedicated playbook. It won't hurt to put links to Confluence / other PRs right in the code to make sure that anyone will be able to recollect what happened in the code. Well... In an ideal world. + Remember: it's very easy to lose context or understanding of something that is not 100% obvious. +- When requesting a change, either generally or on specific lines, double check if your request is clear enough for another person to understand. Consider using [suggestions](https://stackoverflow.com/questions/60311158/how-can-i-suggest-multiple-lines-be-changed-in-markdown), attaching screenshots or links to make requests clearer. + +## Branch name + +The branch name must start with the conventional type (`feat` / `fix` etc), have the ticket number and the ticket name (in lower case) in it. + +Example: a ticket you resolve is named `Introduce new kind of something` and the number is ABC-123. The branch name for a PR from the previous example must either be `feat/ABC-123-introduce-new-kind-of-something` or just `feat/ABC-123`. + +## Commits + +We are using conventional commits: https://www.conventionalcommits.org/en/v1.0.0/#summary +Please make sure your commits match the rules. + +## Testing your changes first + +Kindly test all the changes you've made in all possible scenarios: + +- Different device sizes +- Different platforms +- Different connection states +- Etc. + +Remember that some features work differently on different platforms, devices etc. For example, the `TextInputType.none` does not work on iOS and may lead to a crash / non-openable keyboard. + +## Title naming convention (pay attention ⚠️) + +### Why? + +When we merge a PR into develop (or any other branch), we do `squash merging`. It's when all the commits of another branch are squashed into one commit, with the name usually set to the PR title. + +_It is important to follow this naming convention as we have a script that parses the merged PR titles into a changelog when we release a new version._ + +### Title schema & rules + +Here's a schema of the PR title: `{type}({tickets}): title`, where: + +- `type` is the type of the conventional commit. For example, a new feature must be `feat`, while a bug fix must either be `bug` or `fix`. +- `tickets` (surrounded by `()`!) is/are the number(s) of the ticket(s) this PR resolves. Can either be one (`ABC-123`), or multiple, separated by comma (`ABC-123,ABC-322,ABC-100`). + If a PR doesn't resolve any ticket (for example, it's a minor enhancement or a documentation update) it is okay to leave the PR title without the `{tickets}` part. Don't forget to remove the `()` brackets then. +- `title` usually is the ticket title. If the ticket resolves multiple tickets, use some common name, like `Login updates`. If the ticket doesn't resolve any tickets choose a title that describes the changes best. + +Example: a ticket you resolve is named `Introduce new kind of something` and the number is ABC-123. The PR title must be: `feat(ABC-123): Introduce new kind of something`. + +## Description + +To make it simpler for those checking your work, please tell them about the changes you made and why you made them. It's essential to give a quick explanation because the reviewer might be dealing with a different project or a different scope and might not know much about your work. + +If there is a ticket associated with your work, please include a link to it. + +### Steps to test + +If your works has complex or unclear steps to test, kindly provide those. Something like this would work: + +1. Sign in as ... +2. Go to ... +3. Do ... +4. Expect ... + +If it doesn't require any specific steps to test skip this part. + +### Showcase: videos and screenshots + +Adding videos and screenshots to your work helps reviewers understand what you're doing faster. It lets them see how things are going without having to open the app, catching any issues early on. This way, reviewers only start looking at the code when everything checks out in the videos or pictures. It's a straightforward way to make the review process quicker and easier. Keep in mind that the reviewer might be working in a very different context right now, so any information you provide will be useful. + +In case some changes were requested, kindly attach a new video / screenshot, if required. + +#### Why? + +Switching to another branch, rerunning (possibly) code generation and launching / relaunching the app might be time consuming. If the change is minor, it is worth to provide the reviewer a visual representation of the change, so they won't even need to launch the app on their side to test it out. + +### Add Screenshots in a better way + +If you just drag & drop or upload a screenshot to Github, it will be formatted like this: + +```markdown +![IMAGE_NAME](YOUR_ASSET_URL) +``` + +Which will make the image expand to the max available width. Instead, we should add images like this: + +```markdown +# Width is approximate but usually works just fine. + + +``` + +> 💡 Tip of the day! +> +> Consider adding an `img` [text replacement](https://support.apple.com/en-gb/guide/mac-help/mh35735/mac) to your MacOS settings! + +Notice the difference between these two: + +#### First formatting + +Images are very large and cannot fit the normal screen size. In order to view a screenshot, you need to open it in a separate tab or zoom out the screen (just like it's done on the screenshot) + + +

Figure: Bad example of adding the screenshots

+ +#### Second formatting + +Images are not large, even multiple images can be placed in one row next to each other (normal browser zoom). + + +

Figure: Good example of adding the screenshots

diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..416982f --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1231 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + url: "https://pub.dev" + source: hosted + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + url: "https://pub.dev" + source: hosted + version: "6.7.0" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: b83e8ce46da7228cdd019b5a11205454847f0a971bca59a7529b98df9876889b + url: "https://pub.dev" + source: hosted + version: "9.2.2" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + build_test: + dependency: transitive + description: + name: build_test + sha256: "260dbba934f41b0a42935e9cae1f5731b94f0c3e489dc97bcf8e281265aaa5ae" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + color: + dependency: transitive + description: + name: color + sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5" + url: "https://pub.dev" + source: hosted + version: "1.11.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.7" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95 + url: "https://pub.dev" + source: hosted + version: "11.1.1" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + sha256: "46ecf0e317413dd065547887c43f93f55e9653e83eb98dc13dd07d40dd225325" + url: "https://pub.dev" + source: hosted + version: "5.8.0" + flutter_gen_runner: + dependency: "direct dev" + description: + name: flutter_gen_runner + sha256: "77f0a02fc30d9fcf2549fe874eb3fde091435724904bcbb1af60aa40cbfab1f4" + url: "https://pub.dev" + source: hosted + version: "5.8.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" + url: "https://pub.dev" + source: hosted + version: "9.2.2" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_sticky_header: + dependency: "direct main" + description: + name: flutter_sticky_header + sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" + url: "https://pub.dev" + source: hosted + version: "2.0.16" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + url: "https://pub.dev" + source: hosted + version: "2.5.7" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: c49895c1ecb0ee2a0ec568d39de882e2c299ba26355aa6744ab1001f98cebd15 + url: "https://pub.dev" + source: hosted + version: "8.0.2" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hashcodes: + dependency: transitive + description: + name: hashcodes + sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image_size_getter: + dependency: transitive + description: + name: image_size_getter + sha256: "0511799498340b70993d2dfb34b55a2247b5b801d75a6cdd4543acfcafdb12b0" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + injectable: + dependency: "direct main" + description: + name: injectable + sha256: "5e1556ea1d374fe44cbe846414d9bab346285d3d8a1da5877c01ad0774006068" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + sha256: af403d76c7b18b4217335e0075e950cd0579fd7f8d7bd47ee7c85ada31680ba1 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + url: "https://pub.dev" + source: hosted + version: "6.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "7afc60865a2429d994144f7d66ced2ae4305fe35d82890b8766e3359872d872c" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + monstarlab_lints: + dependency: "direct dev" + description: + name: monstarlab_lints + sha256: fbdde80cf9c9a0ab133b2a6aca403ec987e66ff1ee62dc218eee63c1cf25ce27 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce + url: "https://pub.dev" + source: hosted + version: "8.1.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.dev" + source: hosted + version: "3.0.1" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7" + url: "https://pub.dev" + source: hosted + version: "2.2.14" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + retrofit: + dependency: "direct main" + description: + name: retrofit + sha256: "3c9885ef3dbc5dc4b3fb0a40c972ab52e4dad04d52dac9bba24dfa76cf100451" + url: "https://pub.dev" + source: hosted + version: "4.4.1" + retrofit_generator: + dependency: "direct dev" + description: + name: retrofit_generator + sha256: f76fdb2b66854690d5a332e7364d7561fc9dc2b3c924d7956ab8070495e21f6a + url: "https://pub.dev" + source: hosted + version: "9.1.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_gen_test: + dependency: transitive + description: + name: source_gen_test + sha256: ba9ec63c8f4f4d91021e5e09dd8259dc3bdfbd13cd027d91d761201542bc4a1f + url: "https://pub.dev" + source: hosted + version: "1.1.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + url: "https://pub.dev" + source: hosted + version: "1.25.7" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + test_core: + dependency: transitive + description: + name: test_core + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + theme_tailor: + dependency: "direct dev" + description: + name: theme_tailor + sha256: "176c88b37f56996c2dc7b9e426a2db6fd66f95e1454288f7205ea4faa9380a3a" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + theme_tailor_annotation: + dependency: "direct main" + description: + name: theme_tailor_annotation + sha256: "2990264653b700c7eece3557a5fe5f999cdcd32981aed67023a66004943db08d" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + time: + dependency: transitive + description: + name: time + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + value_layout_builder: + dependency: transitive + description: + name: value_layout_builder + sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa + url: "https://pub.dev" + source: hosted + version: "0.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + url: "https://pub.dev" + source: hosted + version: "1.1.15" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + url: "https://pub.dev" + source: hosted + version: "1.1.12" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + url: "https://pub.dev" + source: hosted + version: "1.1.16" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + url: "https://pub.dev" + source: hosted + version: "5.8.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.4 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2841c34..afee99b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,85 +1,79 @@ name: flutter_template description: A new Flutter application. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 +publish_to: "none" # Private package, prevent accidental publishing + +version: 0.0.1+1 environment: - sdk: ^3.0.0 + sdk: ^3.5.4 dependencies: flutter: sdk: flutter - # UI - cupertino_icons: ^1.0.4 - flutter_svg: ^2.0.7 - nstack: - git: - url: https://github.com/nstack-io/flutter-sdk.git - ref: v0.5.1 - - # Net - dio: ^5.1.2 - jaguar_jwt: ^3.0.0 - - # Utility - device_info_plus: ^9.0.0 - package_info_plus: ^4.0.0 - - # Persistence - shared_preferences: ^2.1.1 - - # Architecture - freezed_annotation: ^2.2.0 - json_annotation: ^4.8.1 - injectable: ^2.1.1 - get_it: ^7.6.0 - auto_route: ^7.1.0 - flutter_bloc: ^8.1.2 - rxdart: ^0.27.7 + # UI & Design + cupertino_icons: ^1.0.8 + flutter_svg: ^2.0.16 + lottie: ^3.1.3 + flutter_sticky_header: ^0.7.0 + auto_size_text: ^3.0.0 + theme_tailor_annotation: ^3.0.1 + + # Networking & API + dio: ^5.7.0 + retrofit: ^4.4.1 + + # State Management, Dependency Injection, Navigation + get_it: ^8.0.2 + injectable: ^2.5.0 + flutter_bloc: ^8.1.6 + auto_route: ^9.2.2 + + # Data Modeling & Serialization + freezed_annotation: ^2.4.4 + json_annotation: ^4.9.0 + + # Device & App Info + device_info_plus: ^11.1.1 + package_info_plus: ^8.1.1 + + # Persistence & Storage + flutter_secure_storage: ^9.2.2 + shared_preferences: ^2.3.3 dev_dependencies: + # Testing flutter_test: sdk: flutter - build_runner: ^2.4.6 - injectable_generator: ^2.1.5 - freezed: ^2.3.3 - json_serializable: ^6.6.2 - auto_route_generator: ^7.0.0 - flutter_lints: ^2.0.1 - # Simplifed work with assets - flutter_gen_runner: ^5.3.1 + # Code Generation + build_runner: ^2.4.13 + freezed: ^2.5.7 + json_serializable: ^6.9.0 + theme_tailor: ^3.0.1 + auto_route_generator: ^9.0.0 + injectable_generator: ^2.6.2 + retrofit_generator: ^9.1.5 + + # Linting & Code Quality + monstarlab_lints: ^1.0.4 + + # Asset Management + flutter_gen_runner: ^5.8.0 flutter_gen: integrations: flutter_svg: true + lottie: true -# The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/icons/ - assets/images/ + - assets/animations/ fonts: - family: Roboto @@ -114,6 +108,3 @@ flutter: - asset: assets/fonts/Roboto-ThinItalic.ttf weight: 100 style: italic - - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy