diff --git a/.env.dev b/.env.dev
new file mode 100644
index 0000000..b7c75e6
--- /dev/null
+++ b/.env.dev
@@ -0,0 +1,8 @@
+# Don't forget, .env should never be commited.
+# If you want to keep your environment variables secret,
+# you might be interested in git-crypt, otherwise add
+# the .env to your .gitignore file.
+
+# DEV
+API_URL=https://dev.com/api/v1
+ANALYTICS_URL=https://dev.analytics.com
diff --git a/.env.prod b/.env.prod
new file mode 100644
index 0000000..ad97203
--- /dev/null
+++ b/.env.prod
@@ -0,0 +1,8 @@
+# Don't forget, .env should never be commited.
+# If you want to keep your environment variables secret,
+# you might be interested in git-crypt, otherwise add
+# the .env to your .gitignore file.
+
+# DEV
+API_URL=https://prod.com/api/v1
+ANALYTICS_URL=https://prod.analytics.com
diff --git a/.fvmrc b/.fvmrc
new file mode 100644
index 0000000..906bbb3
--- /dev/null
+++ b/.fvmrc
@@ -0,0 +1,3 @@
+{
+ "flutter": "3.24.3"
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e844d19
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,135 @@
+# Miscellaneous
+*.class
+*.lock
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Visual Studio Code related
+.classpath
+.project
+.settings/
+.vscode/
+
+# Flutter repo-specific
+/bin/cache/
+/bin/internal/bootstrap.bat
+/bin/internal/bootstrap.sh
+/bin/mingit/
+/dev/benchmarks/mega_gallery/
+/dev/bots/.recipe_deps
+/dev/bots/android_tools/
+/dev/devicelab/ABresults*.json
+/dev/docs/doc/
+/dev/docs/flutter.docs.zip
+/dev/docs/lib/
+/dev/docs/pubspec.yaml
+/dev/integration_tests/**/xcuserdata
+/dev/integration_tests/**/Pods
+/packages/flutter/coverage/
+version
+analysis_benchmark.json
+
+# packages file containing multi-root paths
+.packages.generated
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+**/generated_plugin_registrant.dart
+.packages
+.pub-cache/
+.pub/
+build/
+flutter_*.png
+linked_*.ds
+unlinked.ds
+unlinked_spec.ds
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+**/android/key.properties
+*.jks
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/.last_build_id
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# macOS
+**/macos/Flutter/GeneratedPluginRegistrant.swift
+
+# Coverage
+coverage/
+
+# Symbols
+app.*.symbols
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+!/dev/ci/**/Gemfile.lock
+
+/ios/build/*
+
+# Hide injection files.
+/*.inject.summary
+/*.inject.dart
+**/*.freezed.dart
+**/*.g.dart
+**/*.gr.dart
+**/*.config.dart
+android/app/release/
+
+# don't check in golden failure output
+**/failures/*.png
+
+# FVM Version Cache
+.fvm/
diff --git a/README.md b/README.md
index fb5dd8c..3260657 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,72 @@
-# fvm_monorepo_bloc_boilerplate
-fvm_monorepo_bloc_boilerplate
+# Flutter Monorepo Bloc Boilerplate
+
+## Getting Started
+
+Follow these steps to set up and run the project:
+
+### Prerequisites
+
+- **Git** installed on your machine.
+- **Dart Sdk (dart-sdk)** installed.
+
+ ```bash
+ brew install dart-sdk
+ ```
+
+- **Flutter Version Management (FVM)** installed.
+
+ ```bash
+ brew install fvm
+ ```
+
+- **Melos** installed.
+
+ ```bash
+ fvm dart pub global activate melos
+ ```
+
+### Installation
+
+1. **Clone the Repository**
+
+ ```bash
+ git clone https://github.com/thomashoangvn/fvm_monorepo_bloc_boilerplate.git
+ cd fvm_monorepo_bloc_boilerplate
+ ```
+
+2. **Set Flutter Version with FVM**
+
+ Use FVM to switch to the required Flutter version:
+
+ ```bash
+ fvm install 3.24.3
+ fvm use 3.24.3
+ ```
+
+3. **Install Dependencies**
+
+ Fetch all project dependencies:
+
+ ```bash
+ fvm flutter pub get
+ ```
+
+4. **Bootstrap the Project with Melos**
+
+ Initialize the project using Melos:
+
+ ```bash
+ fvm dart pub global activate melos
+ ```
+
+ ```bash
+ melos bootstrap
+ ```
+
+## Contributing
+
+We appreciate your interest in contributing to FMBB. Feel free to open issues or submit pull requests.
+
+## License
+
+This project is licensed under the [BSD-4-Clause License](LICENSE).
diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml
new file mode 100644
index 0000000..088fb42
--- /dev/null
+++ b/all_lint_rules.yaml
@@ -0,0 +1,227 @@
+# Official list of all Dart & Flutter lint rules:
+# https://dart.dev/tools/linter-rules/all
+
+linter:
+ rules:
+ - always_declare_return_types
+ - always_put_control_body_on_new_line
+ - always_put_required_named_parameters_first
+ - always_specify_types
+ - always_use_package_imports
+ - annotate_overrides
+ - annotate_redeclares
+ - avoid_annotating_with_dynamic
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_catches_without_on_clauses
+ - avoid_catching_errors
+ - avoid_classes_with_only_static_members
+ - avoid_double_and_int_checks
+ - avoid_dynamic_calls
+ - avoid_empty_else
+ - avoid_equals_and_hash_code_on_mutable_classes
+ - avoid_escaping_inner_quotes
+ - avoid_field_initializers_in_const_classes
+ - avoid_final_parameters
+ - avoid_function_literals_in_foreach_calls
+ - avoid_implementing_value_types
+ - avoid_init_to_null
+ - avoid_js_rounded_ints
+ - avoid_multiple_declarations_per_line
+ - avoid_null_checks_in_equality_operators
+ - avoid_positional_boolean_parameters
+ - avoid_print
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_relative_lib_imports
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null_for_void
+ - avoid_returning_this
+ - avoid_setters_without_getters
+ - avoid_shadowing_type_parameters
+ - avoid_single_cascade_in_expression_statements
+ - avoid_slow_async_io
+ - avoid_type_to_string
+ - avoid_types_as_parameter_names
+ - avoid_types_on_closure_parameters
+ - avoid_unnecessary_containers
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - avoid_web_libraries_in_flutter
+ - await_only_futures
+ - camel_case_extensions
+ - camel_case_types
+ - cancel_subscriptions
+ - cascade_invocations
+ - cast_nullable_to_non_nullable
+ - close_sinks
+ - collection_methods_unrelated_type
+ - combinators_ordering
+ - comment_references
+ - conditional_uri_does_not_exist
+ - constant_identifier_names
+ - control_flow_in_finally
+ - curly_braces_in_flow_control_structures
+ - dangling_library_doc_comments
+ - depend_on_referenced_packages
+ - deprecated_consistency
+ - deprecated_member_use_from_same_package
+ - diagnostic_describe_all_properties
+ - directives_ordering
+ - discarded_futures
+ - do_not_use_environment
+ - document_ignores
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - eol_at_end_of_file
+ - exhaustive_cases
+ - file_names
+ - flutter_style_todos
+ - hash_and_equals
+ - implementation_imports
+ - implicit_call_tearoffs
+ - implicit_reopen
+ - invalid_case_patterns
+ - invalid_runtime_check_with_js_interop_types
+ - join_return_with_assignment
+ - leading_newlines_in_multiline_strings
+ - library_annotations
+ - library_names
+ - library_prefixes
+ - library_private_types_in_public_api
+ - lines_longer_than_80_chars
+ - literal_only_boolean_expressions
+ - matching_super_parameters
+ - missing_code_block_language_in_doc_comment
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_default_cases
+ - no_duplicate_case_values
+ - no_leading_underscores_for_library_prefixes
+ - no_leading_underscores_for_local_identifiers
+ - no_literal_bool_comparisons
+ - no_logic_in_create_state
+ - no_runtimeType_toString
+ - no_self_assignments
+ - no_wildcard_variable_uses
+ - non_constant_identifier_names
+ - noop_primitive_operations
+ - null_check_on_nullable_type_parameter
+ - null_closures
+ - omit_local_variable_types
+ - one_member_abstracts
+ - only_throw_errors
+ - overridden_fields
+ - package_api_docs
+ - package_names
+ - package_prefixed_library_names
+ - parameter_assignments
+ - prefer_adjacent_string_concatenation
+ - prefer_asserts_in_initializer_lists
+ - prefer_asserts_with_message
+ - prefer_collection_literals
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_const_literals_to_create_immutables
+ - prefer_constructors_over_static_methods
+ - prefer_contains
+ - prefer_double_quotes
+ - prefer_expression_function_bodies
+ - prefer_final_fields
+ - prefer_final_in_for_each
+ - prefer_final_locals
+ - prefer_final_parameters
+ - prefer_for_elements_to_map_fromIterable
+ - prefer_foreach
+ - prefer_function_declarations_over_variables
+ - prefer_generic_function_type_aliases
+ - prefer_if_elements_to_conditional_expressions
+ - prefer_if_null_operators
+ - prefer_initializing_formals
+ - prefer_inlined_adds
+ - prefer_int_literals
+ - prefer_interpolation_to_compose_strings
+ - prefer_is_empty
+ - prefer_is_not_empty
+ - prefer_is_not_operator
+ - prefer_iterable_whereType
+ - prefer_mixin
+ - prefer_null_aware_method_calls
+ - prefer_null_aware_operators
+ - prefer_relative_imports
+ - prefer_single_quotes
+ - prefer_spread_collections
+ - prefer_typing_uninitialized_variables
+ - prefer_void_to_null
+ - provide_deprecation_message
+ - public_member_api_docs
+ - recursive_getters
+ - require_trailing_commas
+ - secure_pubspec_urls
+ - sized_box_for_whitespace
+ - sized_box_shrink_expand
+ - slash_for_doc_comments
+ - sort_child_properties_last
+ - sort_constructors_first
+ - sort_pub_dependencies
+ - sort_unnamed_constructors_first
+ - test_types_in_equals
+ - throw_in_finally
+ - tighten_type_of_initializing_formals
+ - type_annotate_public_apis
+ - type_init_formals
+ - type_literal_in_constant_pattern
+ - unawaited_futures
+ - unintended_html_in_doc_comment
+ - unnecessary_await_in_return
+ - unnecessary_brace_in_string_interps
+ - unnecessary_breaks
+ - unnecessary_const
+ - unnecessary_constructor_name
+ - unnecessary_final
+ - unnecessary_getters_setters
+ - unnecessary_lambdas
+ - unnecessary_late
+ - unnecessary_library_directive
+ - unnecessary_library_name
+ - unnecessary_new
+ - unnecessary_null_aware_assignments
+ - unnecessary_null_aware_operator_on_extension_on_nullable
+ - unnecessary_null_checks
+ - unnecessary_null_in_if_null_operators
+ - unnecessary_nullable_for_final_variable_declarations
+ - unnecessary_overrides
+ - unnecessary_parenthesis
+ - unnecessary_raw_strings
+ - unnecessary_statements
+ - unnecessary_string_escapes
+ - unnecessary_string_interpolations
+ - unnecessary_this
+ - unnecessary_to_list_in_spreads
+ - unreachable_from_main
+ - unrelated_type_equality_checks
+ - unsafe_html
+ - use_build_context_synchronously
+ - use_colored_box
+ - use_decorated_box
+ - use_enums
+ - use_full_hex_values_for_flutter_colors
+ - use_function_type_syntax_for_parameters
+ - use_if_null_to_convert_nulls_to_bools
+ - use_is_even_rather_than_modulo
+ - use_key_in_widget_constructors
+ - use_late_for_private_fields_and_variables
+ - use_named_constants
+ - use_raw_strings
+ - use_rethrow_when_possible
+ - use_setters_to_change_properties
+ - use_string_buffers
+ - use_string_in_part_of_directives
+ - use_super_parameters
+ - use_test_throws_matchers
+ - use_to_and_as_if_applicable
+ - valid_regexps
+ - void_checks
\ No newline at end of file
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..1544bcc
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,138 @@
+include: all_lint_rules.yaml
+
+analyzer:
+ errors:
+ # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts.
+ # We explicitly enabled even conflicting rules and are fixing the conflict
+ # in this file
+ included_file_warning: ignore
+ # https://github.com/rrousselGit/freezed/issues/488
+ invalid_annotation_target: ignore
+
+ exclude:
+ # Exclude generated files
+ - "**/generated_plugin_registrant.dart"
+ - "deps/**"
+ - "**/overridens/**"
+ - "**/animated_box_decoration/**"
+ - "**.g.dart"
+
+# DCM (formerly Dart Code Metrics) is a toolkit that helps to identify and fix problems.
+# https://dcm.dev/docs/getting-started/
+dart_code_metrics:
+ extends:
+ - package:dart_code_metrics_presets/all.yaml
+ rules:
+ # will be removed
+ - no-magic-string: false
+ - no-magic-number: false
+ - parameters-ordering: false
+ - avoid-barrel-files: false
+ - avoid-inferrable-type-arguments: false
+ - avoid-explicit-type-declaration: false
+ - member-ordering:
+ order:
+ - constructors
+ - public-fields
+ - private-fields
+ - close-method
+ - dispose-method
+ widgets-order:
+ - constructor
+ - build-method
+ - init-state-method
+ - did-change-dependencies-method
+ - did-update-widget-method
+ - dispose-method
+ - avoid-non-null-assertion:
+ skip-checked-fields: true
+ - prefer-moving-to-variable:
+ allowed-duplicated-chains: 5
+ - avoid-late-keyword:
+ allow-initialized: true
+ ignored-types:
+ - StreamSubscription
+ - avoid-nested-conditional-expressions:
+ acceptable-level: 2
+ - prefer-correct-throws: false
+ - prefer-commenting-analyzer-ignores: false
+ - avoid-similar-names:
+ show-similarity: true
+ similarity-threshold: 0.9
+ - prefer-boolean-prefixes:
+ ignored-names:
+ - newValue
+ - prefer-named-parameters:
+ max-number: 3
+ - prefer-extracting-function-callbacks: false
+
+
+linter:
+ rules:
+ # Boring as it sometimes force a line of 81 characters to be split in two.
+ # As long as we try to respect that 80 characters limit, going slightly
+ # above is fine.
+ lines_longer_than_80_chars: false
+
+ # Not quite suitable for Flutter, which may have a `build` method with a single
+ # return, but that return is still complex enough that a "body" is worth it.
+ prefer_expression_function_bodies: false
+
+ # There are situations where we voluntarily want to catch everything,
+ # especially as a library.
+ avoid_catches_without_on_clauses: false
+
+ # Conflicts with `always_specify_types` and other rules.
+ # As per Dart guidelines, we want to avoid unnecessary types
+ # to make the code more readable.
+ # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables
+ omit_local_variable_types: false
+ avoid_types_on_closure_parameters: false
+
+ # Far too verbose, and not that big of a deal when using parameter_assignments
+ prefer_final_parameters: false
+
+ # Conflicts with `prefer_single_quotes`
+ # Single quotes are easier to type and don't compromise on readability.
+ prefer_double_quotes: false
+
+ # This project doesn't use Flutter-style todos
+ flutter_style_todos: false
+
+ # Conflicts with disabling `implicit-dynamic`
+ avoid_annotating_with_dynamic: false
+
+ # Incompatible with `prefer_final_locals`
+ # Having immutable local variables makes larger functions more predictible
+ # so we will use `prefer_final_locals` instead.
+ unnecessary_final: false
+
+ # conflicts with `prefer_relative_imports`
+ prefer_relative_imports: false
+
+ # Disabled for now until we have NNBD as it otherwise conflicts with `missing_return`
+ no_default_cases: false
+
+ # False positive, null checks don't need a message
+ prefer_asserts_with_message: false
+
+ # Too many false positive (builders)
+ diagnostic_describe_all_properties: false
+
+ # false positives (setter-like functions)
+ avoid_positional_boolean_parameters: false
+
+ # Does not apply to providers
+ prefer_const_constructors_in_immutables: false
+
+ ## Disabled rules because the repository doesn't respect them (yet)
+ comment_references: false
+ avoid_classes_with_only_static_members: false
+ do_not_use_environment: false
+ discarded_futures: false
+ use_decorated_box: false
+ public_member_api_docs: false
+ one_member_abstracts: false
+ sort_constructors_first: false
+ sort_child_properties_last: false
+ document_ignores: false
\ No newline at end of file
diff --git a/app/.metadata b/app/.metadata
new file mode 100644
index 0000000..2d1be89
--- /dev/null
+++ b/app/.metadata
@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "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
+ - platform: ios
+ create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ - platform: linux
+ create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ - platform: macos
+ create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ - platform: web
+ create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
+ - platform: windows
+ 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/app/android/.gitignore b/app/android/.gitignore
new file mode 100644
index 0000000..55afd91
--- /dev/null
+++ b/app/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle
new file mode 100644
index 0000000..54b6d53
--- /dev/null
+++ b/app/android/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+android {
+ namespace = "com.example.app"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ 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.app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6ee3df0
--- /dev/null
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/kotlin/com/example/app/MainActivity.kt b/app/android/app/src/main/kotlin/com/example/app/MainActivity.kt
new file mode 100644
index 0000000..026d9a9
--- /dev/null
+++ b/app/android/app/src/main/kotlin/com/example/app/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/app/android/app/src/main/res/drawable-v21/launch_background.xml b/app/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/drawable/launch_background.xml b/app/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/app/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/values-night/styles.xml b/app/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/app/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/app/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/android/build.gradle b/app/android/build.gradle
new file mode 100644
index 0000000..d2ffbff
--- /dev/null
+++ b/app/android/build.gradle
@@ -0,0 +1,18 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/app/android/gradle.properties b/app/android/gradle.properties
new file mode 100644
index 0000000..2597170
--- /dev/null
+++ b/app/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7bb2df6
--- /dev/null
+++ b/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
diff --git a/app/android/settings.gradle b/app/android/settings.gradle
new file mode 100644
index 0000000..b9e43bd
--- /dev/null
+++ b/app/android/settings.gradle
@@ -0,0 +1,25 @@
+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
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "8.1.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.8.22" apply false
+}
+
+include ":app"
diff --git a/app/devtools_options.yaml b/app/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/app/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/app/ios/.gitignore b/app/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/app/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/app/ios/Flutter/AppFrameworkInfo.plist b/app/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..7c56964
--- /dev/null
+++ b/app/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/app/ios/Flutter/Debug.xcconfig b/app/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..ec97fc6
--- /dev/null
+++ b/app/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/app/ios/Flutter/Release.xcconfig b/app/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..a6bc54d
--- /dev/null
+++ b/app/ios/Flutter/Release.xcconfig
@@ -0,0 +1,3 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include? "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
+#include "Generated.xcconfig"
diff --git a/app/ios/Podfile b/app/ios/Podfile
new file mode 100644
index 0000000..164df53
--- /dev/null
+++ b/app/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..d0e8038
--- /dev/null
+++ b/app/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,749 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 425CD6CFA401537AE8BF505F /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A6EB4AA0E9481FF907CCA32 /* Pods_RunnerTests.framework */; };
+ 456B693358D48777BCAE3CA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3DA5B12908B1E7A5A59C538 /* Pods_Runner.framework */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 1F8CAAE8541E4EEE2D2A859A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ 2F496530966018908EC99B38 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 360F51F1853457F44E542B77 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 4953B94E7C58FA13659611BC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 60E9000D6E63B296A1D9E487 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 6D5BC415494363A16C2EE956 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 8A6EB4AA0E9481FF907CCA32 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ B3DA5B12908B1E7A5A59C538 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 456B693358D48777BCAE3CA0 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ B60B7BD87976D6F5A8514C6D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 425CD6CFA401537AE8BF505F /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ D9775E02DF6FF81005DFC1CD /* Pods */,
+ E413E863D59FF9B299879E8E /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ D9775E02DF6FF81005DFC1CD /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 360F51F1853457F44E542B77 /* Pods-Runner.debug.xcconfig */,
+ 6D5BC415494363A16C2EE956 /* Pods-Runner.release.xcconfig */,
+ 4953B94E7C58FA13659611BC /* Pods-Runner.profile.xcconfig */,
+ 2F496530966018908EC99B38 /* Pods-RunnerTests.debug.xcconfig */,
+ 60E9000D6E63B296A1D9E487 /* Pods-RunnerTests.release.xcconfig */,
+ 1F8CAAE8541E4EEE2D2A859A /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ E413E863D59FF9B299879E8E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ B3DA5B12908B1E7A5A59C538 /* Pods_Runner.framework */,
+ 8A6EB4AA0E9481FF907CCA32 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ C219CBF6326AB5C1B1FEBEBD /* [CP] Check Pods Manifest.lock */,
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ B60B7BD87976D6F5A8514C6D /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 1187E3FD219E4F1FF1AAA38C /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ A0C87628D40FF2E3BD12E3B1 /* [CP] Embed Pods Frameworks */,
+ F550E787ECC72F6B4696F768 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 331C8080294A63A400263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ 331C8080294A63A400263BE5 /* RunnerTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 331C807F294A63A400263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 1187E3FD219E4F1FF1AAA38C /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+ A0C87628D40FF2E3BD12E3B1 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C219CBF6326AB5C1B1FEBEBD /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ F550E787ECC72F6B4696F768 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 331C807D294A63A400263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = G6GSP49983;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 331C8088294A63A400263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2F496530966018908EC99B38 /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Debug;
+ };
+ 331C8089294A63A400263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 60E9000D6E63B296A1D9E487 /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Release;
+ };
+ 331C808A294A63A400263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 1F8CAAE8541E4EEE2D2A859A /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = G6GSP49983;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = G6GSP49983;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C8088294A63A400263BE5 /* Debug */,
+ 331C8089294A63A400263BE5 /* Release */,
+ 331C808A294A63A400263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..8e3ca5d
--- /dev/null
+++ b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ios/Runner.xcworkspace/contents.xcworkspacedata b/app/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..21a3cc1
--- /dev/null
+++ b/app/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..6266644
--- /dev/null
+++ b/app/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import Flutter
+import UIKit
+
+@main
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..7353c41
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..6ed2d93
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cd7b00
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..fe73094
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..321773c
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..797d452
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..502f463
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..0ec3034
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..e9f5fea
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..84ac32a
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..8953cba
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..0467bf1
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/app/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/app/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ios/Runner/Base.lproj/Main.storyboard b/app/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/app/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist
new file mode 100644
index 0000000..c2e05c5
--- /dev/null
+++ b/app/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ App
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ app
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/app/ios/Runner/Runner-Bridging-Header.h b/app/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/app/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/app/ios/RunnerTests/RunnerTests.swift b/app/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/app/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/app/lib/main.dart b/app/lib/main.dart
new file mode 100644
index 0000000..f6f93ca
--- /dev/null
+++ b/app/lib/main.dart
@@ -0,0 +1,25 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/flutter_native_splash.dart';
+import 'package:flutter/widgets.dart';
+
+/// The entry point of the application.
+///
+/// This function initializes necessary services before starting the app's main logic.
+///
+/// The [MainBinding] class is responsible for setting up the app's core components
+/// and handling the [WidgetsBinding]. The [FlutterNativeSplash.preserve] call
+/// ensures that the splash screen is displayed while the asynchronous initialization completes.
+void main() => MainBinding(
+ mainCallback: (WidgetsBinding binding) async {
+ // Keeps the native splash screen until Flutter is ready
+ FlutterNativeSplash.preserve(widgetsBinding: binding);
+
+ // TODO: Initialize other services here if needed.
+ },
+ );
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
new file mode 100644
index 0000000..b554f24
--- /dev/null
+++ b/app/pubspec.yaml
@@ -0,0 +1,47 @@
+name: app_name
+description: The main app of the flutter monorepo bloc boilerplate.
+publish_to: none
+version: 0.0.1+1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ cupertino_icons: ^1.0.8
+ deps:
+ path: ../deps
+ flutter:
+ sdk: flutter
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
+ assets:
+ - ../design/assets/icons/
+ - ../design/assets/anims/
+ - ../design/assets/images/
+
+ fonts:
+ - family: Inter
+ fonts:
+ - asset: ../design/assets/fonts/Inter/Inter-Black.ttf
+ weight: 900
+ - asset: ../design/assets/fonts/Inter/Inter-ExtraBold.ttf
+ weight: 800
+ - asset: ../design/assets/fonts/Inter/Inter-Bold.ttf
+ weight: 700
+ - asset: ../design/assets/fonts/Inter/Inter-SemiBold.ttf
+ weight: 600
+ - asset: ../design/assets/fonts/Inter/Inter-Medium.ttf
+ weight: 500
+ - asset: ../design/assets/fonts/Inter/Inter-Regular.ttf
+ weight: 400
+ - asset: ../design/assets/fonts/Inter/Inter-Light.ttf
+ weight: 300
+ - asset: ../design/assets/fonts/Inter/Inter-ExtraLight.ttf
+ weight: 200
+ - asset: ../design/assets/fonts/Inter/Inter-Thin.ttf
+ weight: 100
\ No newline at end of file
diff --git a/app/pubspec_overrides.yaml b/app/pubspec_overrides.yaml
new file mode 100644
index 0000000..300314f
--- /dev/null
+++ b/app/pubspec_overrides.yaml
@@ -0,0 +1,14 @@
+# melos_managed_dependency_overrides: deps,design,feature_auth,feature_core,feature_user,infrastructure
+dependency_overrides:
+ deps:
+ path: ../deps
+ design:
+ path: ../design
+ feature_auth:
+ path: ../features/auth
+ feature_core:
+ path: ../features/_core
+ feature_user:
+ path: ../features/user
+ infrastructure:
+ path: ../infrastructure
diff --git a/deps/build.yaml b/deps/build.yaml
new file mode 100644
index 0000000..306d957
--- /dev/null
+++ b/deps/build.yaml
@@ -0,0 +1,22 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+
+targets:
+ $default:
+ builders:
+ # Injectable Generators
+ #
+ # Injectable is used to generate dependency injection code for the app. It automatically generates
+ # registration code for classes annotated with `@Injectable` or `@LazySingleton`.
+
+ # This section controls the Injectable dependency injection code generation.
+ injectable_generator:injectable_builder:
+ generate_for:
+ include:
+ - lib/locator/locator.dart
\ No newline at end of file
diff --git a/deps/devtools_options.yaml b/deps/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/deps/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/deps/lib/design/design.dart b/deps/lib/design/design.dart
new file mode 100644
index 0000000..977094e
--- /dev/null
+++ b/deps/lib/design/design.dart
@@ -0,0 +1,12 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// This file exports the entire `design` package, allowing it to be used in
+// other parts of the project. By exporting `design.dart`, any module or
+// component that imports this file will have access to all the elements
+// defined in the `design` package.
+
+export 'package:design/design.dart';
diff --git a/deps/lib/features/features.dart b/deps/lib/features/features.dart
new file mode 100644
index 0000000..792df7c
--- /dev/null
+++ b/deps/lib/features/features.dart
@@ -0,0 +1 @@
+export 'package:feature_core/core.dart';
diff --git a/deps/lib/infrastructure/infrastructure.dart b/deps/lib/infrastructure/infrastructure.dart
new file mode 100644
index 0000000..d8752a8
--- /dev/null
+++ b/deps/lib/infrastructure/infrastructure.dart
@@ -0,0 +1,13 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// This file exports the entire `infrastructure` package, providing access to
+// various foundational components, services, utilities, and configurations used
+// throughout the application. By exporting `infrastructure.dart`, other modules
+// can leverage the complete infrastructure layer of the application from a
+// single import statement.
+
+export 'package:infrastructure/infrastructure.dart';
diff --git a/deps/lib/locator/locator.dart b/deps/lib/locator/locator.dart
new file mode 100644
index 0000000..8fb65b7
--- /dev/null
+++ b/deps/lib/locator/locator.dart
@@ -0,0 +1,27 @@
+import 'package:feature_core/_di/_di.dart';
+import 'package:infrastructure/_core/_di/_di.dart';
+
+import '../packages/get_it.dart';
+import '../packages/injectable.dart';
+import 'locator.config.dart';
+
+/// Global instance of [GetIt] for dependency injection.
+final GetIt locator = GetIt.instance;
+
+/// Initializes the service locator [GetIt] with infrastructure and feature dependencies.
+///
+/// This function configures [GetIt] to provide dependencies for different layers of the application.
+/// It initializes dependencies for both infrastructure and features.
+///
+/// [env] is a string representing the current environment (e.g., development, production).
+@InjectableInit()
+Future initLocator(String env) async {
+ // Inject infrastructure-level dependencies.
+ await injectInfrastructure(di: locator, env: env);
+
+ // Inject feature-level dependencies.
+ injectAllFeatures(di: locator, env: env);
+
+ // Finalize the initialization of dependencies.
+ locator.init(environment: env);
+}
diff --git a/deps/lib/packages/adaptive_theme.dart b/deps/lib/packages/adaptive_theme.dart
new file mode 100644
index 0000000..c2bc072
--- /dev/null
+++ b/deps/lib/packages/adaptive_theme.dart
@@ -0,0 +1 @@
+export 'package:adaptive_theme/adaptive_theme.dart';
diff --git a/deps/lib/packages/auto_route.dart b/deps/lib/packages/auto_route.dart
new file mode 100644
index 0000000..0ddb22f
--- /dev/null
+++ b/deps/lib/packages/auto_route.dart
@@ -0,0 +1 @@
+export 'package:auto_route/auto_route.dart';
diff --git a/deps/lib/packages/back_button_interceptor.dart b/deps/lib/packages/back_button_interceptor.dart
new file mode 100644
index 0000000..5ae0eef
--- /dev/null
+++ b/deps/lib/packages/back_button_interceptor.dart
@@ -0,0 +1 @@
+export 'package:back_button_interceptor/back_button_interceptor.dart';
diff --git a/deps/lib/packages/dio.dart b/deps/lib/packages/dio.dart
new file mode 100644
index 0000000..dcf16a0
--- /dev/null
+++ b/deps/lib/packages/dio.dart
@@ -0,0 +1 @@
+export 'package:dio/dio.dart';
diff --git a/deps/lib/packages/dio_smart_retry.dart b/deps/lib/packages/dio_smart_retry.dart
new file mode 100644
index 0000000..9912b3b
--- /dev/null
+++ b/deps/lib/packages/dio_smart_retry.dart
@@ -0,0 +1 @@
+export 'package:dio_smart_retry/dio_smart_retry.dart';
diff --git a/deps/lib/packages/envied.dart b/deps/lib/packages/envied.dart
new file mode 100644
index 0000000..e1b8a7f
--- /dev/null
+++ b/deps/lib/packages/envied.dart
@@ -0,0 +1 @@
+export 'package:envied/envied.dart';
diff --git a/deps/lib/packages/flutter_bloc.dart b/deps/lib/packages/flutter_bloc.dart
new file mode 100644
index 0000000..f3e1ae3
--- /dev/null
+++ b/deps/lib/packages/flutter_bloc.dart
@@ -0,0 +1 @@
+export 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/deps/lib/packages/flutter_native_splash.dart b/deps/lib/packages/flutter_native_splash.dart
new file mode 100644
index 0000000..ab99e6a
--- /dev/null
+++ b/deps/lib/packages/flutter_native_splash.dart
@@ -0,0 +1 @@
+export 'package:flutter_native_splash/flutter_native_splash.dart';
diff --git a/deps/lib/packages/flutter_secure_storage.dart b/deps/lib/packages/flutter_secure_storage.dart
new file mode 100644
index 0000000..bef146c
--- /dev/null
+++ b/deps/lib/packages/flutter_secure_storage.dart
@@ -0,0 +1 @@
+export 'package:flutter_secure_storage/flutter_secure_storage.dart';
diff --git a/deps/lib/packages/flutter_svg.dart b/deps/lib/packages/flutter_svg.dart
new file mode 100644
index 0000000..f84095a
--- /dev/null
+++ b/deps/lib/packages/flutter_svg.dart
@@ -0,0 +1 @@
+export 'package:flutter_svg/flutter_svg.dart';
diff --git a/deps/lib/packages/fpdart.dart b/deps/lib/packages/fpdart.dart
new file mode 100644
index 0000000..6c8fc97
--- /dev/null
+++ b/deps/lib/packages/fpdart.dart
@@ -0,0 +1 @@
+export 'package:fpdart/fpdart.dart' hide State;
diff --git a/deps/lib/packages/freezed_annotation.dart b/deps/lib/packages/freezed_annotation.dart
new file mode 100644
index 0000000..2c2954e
--- /dev/null
+++ b/deps/lib/packages/freezed_annotation.dart
@@ -0,0 +1 @@
+export 'package:freezed_annotation/freezed_annotation.dart';
diff --git a/deps/lib/packages/get_it.dart b/deps/lib/packages/get_it.dart
new file mode 100644
index 0000000..40b088f
--- /dev/null
+++ b/deps/lib/packages/get_it.dart
@@ -0,0 +1 @@
+export 'package:get_it/get_it.dart';
diff --git a/deps/lib/packages/hydrated_bloc.dart b/deps/lib/packages/hydrated_bloc.dart
new file mode 100644
index 0000000..57825a7
--- /dev/null
+++ b/deps/lib/packages/hydrated_bloc.dart
@@ -0,0 +1 @@
+export 'package:hydrated_bloc/hydrated_bloc.dart';
diff --git a/deps/lib/packages/infinite_scroll_pagination.dart b/deps/lib/packages/infinite_scroll_pagination.dart
new file mode 100644
index 0000000..476de9c
--- /dev/null
+++ b/deps/lib/packages/infinite_scroll_pagination.dart
@@ -0,0 +1 @@
+export 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
diff --git a/deps/lib/packages/injectable.dart b/deps/lib/packages/injectable.dart
new file mode 100644
index 0000000..a6e62bf
--- /dev/null
+++ b/deps/lib/packages/injectable.dart
@@ -0,0 +1 @@
+export 'package:injectable/injectable.dart';
diff --git a/deps/lib/packages/internet_connection_checker_plus.dart b/deps/lib/packages/internet_connection_checker_plus.dart
new file mode 100644
index 0000000..65f90f1
--- /dev/null
+++ b/deps/lib/packages/internet_connection_checker_plus.dart
@@ -0,0 +1 @@
+export 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
diff --git a/deps/lib/packages/intl.dart b/deps/lib/packages/intl.dart
new file mode 100644
index 0000000..dfd44ed
--- /dev/null
+++ b/deps/lib/packages/intl.dart
@@ -0,0 +1 @@
+export 'package:intl/intl.dart';
diff --git a/deps/lib/packages/lottie.dart b/deps/lib/packages/lottie.dart
new file mode 100644
index 0000000..800a186
--- /dev/null
+++ b/deps/lib/packages/lottie.dart
@@ -0,0 +1 @@
+export 'package:lottie/lottie.dart';
diff --git a/deps/lib/packages/package_info_plus.dart b/deps/lib/packages/package_info_plus.dart
new file mode 100644
index 0000000..edf50d6
--- /dev/null
+++ b/deps/lib/packages/package_info_plus.dart
@@ -0,0 +1 @@
+export 'package:package_info_plus/package_info_plus.dart';
diff --git a/deps/lib/packages/path_provider.dart b/deps/lib/packages/path_provider.dart
new file mode 100644
index 0000000..5eb9209
--- /dev/null
+++ b/deps/lib/packages/path_provider.dart
@@ -0,0 +1 @@
+export 'package:path_provider/path_provider.dart';
diff --git a/deps/lib/packages/permission_handler.dart b/deps/lib/packages/permission_handler.dart
new file mode 100644
index 0000000..f0edcca
--- /dev/null
+++ b/deps/lib/packages/permission_handler.dart
@@ -0,0 +1 @@
+export 'package:permission_handler/permission_handler.dart';
diff --git a/deps/lib/packages/reactive_forms.dart b/deps/lib/packages/reactive_forms.dart
new file mode 100644
index 0000000..bb8c99a
--- /dev/null
+++ b/deps/lib/packages/reactive_forms.dart
@@ -0,0 +1 @@
+export 'package:reactive_forms/reactive_forms.dart';
diff --git a/deps/lib/packages/styled_text.dart b/deps/lib/packages/styled_text.dart
new file mode 100644
index 0000000..e8e7a1a
--- /dev/null
+++ b/deps/lib/packages/styled_text.dart
@@ -0,0 +1 @@
+export 'package:styled_text/styled_text.dart';
diff --git a/deps/lib/packages/talker_bloc_logger.dart b/deps/lib/packages/talker_bloc_logger.dart
new file mode 100644
index 0000000..5fc3076
--- /dev/null
+++ b/deps/lib/packages/talker_bloc_logger.dart
@@ -0,0 +1 @@
+export 'package:talker_bloc_logger/talker_bloc_logger.dart';
diff --git a/deps/lib/packages/talker_dio_logger.dart b/deps/lib/packages/talker_dio_logger.dart
new file mode 100644
index 0000000..19cc134
--- /dev/null
+++ b/deps/lib/packages/talker_dio_logger.dart
@@ -0,0 +1 @@
+export 'package:talker_dio_logger/talker_dio_logger.dart';
diff --git a/deps/lib/packages/talker_flutter.dart b/deps/lib/packages/talker_flutter.dart
new file mode 100644
index 0000000..0e1e024
--- /dev/null
+++ b/deps/lib/packages/talker_flutter.dart
@@ -0,0 +1 @@
+export 'package:talker_flutter/talker_flutter.dart';
diff --git a/deps/lib/packages/universal_html.dart b/deps/lib/packages/universal_html.dart
new file mode 100644
index 0000000..f0a0ba4
--- /dev/null
+++ b/deps/lib/packages/universal_html.dart
@@ -0,0 +1 @@
+export 'package:universal_html/html.dart';
diff --git a/deps/pubspec.yaml b/deps/pubspec.yaml
new file mode 100644
index 0000000..4fad540
--- /dev/null
+++ b/deps/pubspec.yaml
@@ -0,0 +1,58 @@
+name: deps
+description: The dependencies of the flutter advanced boilerplate.
+publish_to: none
+version: 0.0.1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ adaptive_theme: ^3.6.0
+ auto_route: ^9.2.2
+ back_button_interceptor: ^7.0.3
+ design:
+ path: ../design
+ dio: ^5.7.0
+ dio_smart_retry: ^6.0.0
+ envied: ^0.5.4+1
+ feature_core:
+ path: ../features/_core
+ flutter:
+ sdk: flutter
+ flutter_bloc: ^8.1.6
+ flutter_native_splash: ^2.4.1
+ flutter_secure_storage: ^9.2.2
+ flutter_svg: ^2.0.10+1
+ fpdart: ^1.1.0
+ freezed_annotation: ^2.4.4
+ get_it: ^7.7.0
+ hydrated_bloc: ^9.1.5
+ infinite_scroll_pagination: ^4.0.0
+ infrastructure:
+ path: ../infrastructure
+ injectable: ^2.4.4
+ internet_connection_checker_plus: ^2.5.1
+ intl: ^0.19.0
+ lottie: ^3.1.2
+ package_info_plus: ^8.0.2
+ path_provider: ^2.1.4
+ permission_handler: ^11.3.1
+ reactive_forms: ^17.0.1
+ slang: ^3.31.2
+ slang_flutter: ^3.31.0
+ styled_text: ^8.1.0
+ talker_bloc_logger: ^4.4.1
+ talker_dio_logger: ^4.4.1
+ talker_flutter: ^4.4.1
+ universal_html: ^2.2.4
+
+dependency_overrides:
+ flutter_secure_storage_web:
+ git:
+ url: https://github.com/fikretsengul/flutter_secure_storage.git
+ path: flutter_secure_storage_web
+ ref: develop
+
+dev_dependencies:
+ build_runner: ^2.4.13
+ injectable_generator: ^2.6.2
diff --git a/deps/pubspec_overrides.yaml b/deps/pubspec_overrides.yaml
new file mode 100644
index 0000000..7ea0bc1
--- /dev/null
+++ b/deps/pubspec_overrides.yaml
@@ -0,0 +1,17 @@
+# melos_managed_dependency_overrides: design,feature_auth,feature_core,feature_user,infrastructure,flutter_secure_storage_web
+dependency_overrides:
+ design:
+ path: ../design
+ feature_auth:
+ path: ../features/auth
+ feature_core:
+ path: ../features/_core
+ feature_user:
+ path: ../features/user
+ infrastructure:
+ path: ../infrastructure
+ flutter_secure_storage_web:
+ git:
+ url: https://github.com/fikretsengul/flutter_secure_storage.git
+ ref: develop
+ path: flutter_secure_storage_web
diff --git a/design/assets/fonts/Inter/Inter-Black.ttf b/design/assets/fonts/Inter/Inter-Black.ttf
new file mode 100644
index 0000000..5653757
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Black.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-Bold.ttf b/design/assets/fonts/Inter/Inter-Bold.ttf
new file mode 100644
index 0000000..e98b84c
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Bold.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-ExtraBold.ttf b/design/assets/fonts/Inter/Inter-ExtraBold.ttf
new file mode 100644
index 0000000..7f16a0f
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-ExtraBold.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-ExtraLight.ttf b/design/assets/fonts/Inter/Inter-ExtraLight.ttf
new file mode 100644
index 0000000..69426a3
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-ExtraLight.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-Light.ttf b/design/assets/fonts/Inter/Inter-Light.ttf
new file mode 100644
index 0000000..a5f0736
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Light.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-Medium.ttf b/design/assets/fonts/Inter/Inter-Medium.ttf
new file mode 100644
index 0000000..721147d
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Medium.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-Regular.ttf b/design/assets/fonts/Inter/Inter-Regular.ttf
new file mode 100644
index 0000000..96fd6a1
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Regular.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-SemiBold.ttf b/design/assets/fonts/Inter/Inter-SemiBold.ttf
new file mode 100644
index 0000000..ddb2792
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-SemiBold.ttf differ
diff --git a/design/assets/fonts/Inter/Inter-Thin.ttf b/design/assets/fonts/Inter/Inter-Thin.ttf
new file mode 100644
index 0000000..76be625
Binary files /dev/null and b/design/assets/fonts/Inter/Inter-Thin.ttf differ
diff --git a/design/build.yaml b/design/build.yaml
new file mode 100644
index 0000000..59d069d
--- /dev/null
+++ b/design/build.yaml
@@ -0,0 +1,30 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+
+targets:
+ $default:
+ builders:
+ # Slang Generators
+ #
+ # Slang is used for internationalization (i18n) by generating translation classes. This configuration
+ # controls how translations are handled and generated.
+
+ # This section configures the Slang package for generating i18n (internationalization) translation classes.
+ slang_build_runner:
+ options:
+ locale_handling: false
+ translation_class_visibility: public
+ fallback_strategy: base_locale
+ input_directory: lib/_core/_i18n
+ output_directory: lib/_core/_i18n
+ output_file_name: translations.g.dart
+ key_case: camel
+ key_map_case: camel
+ param_case: camel
+ flat_map: false
\ No newline at end of file
diff --git a/design/devtools_options.yaml b/design/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/design/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/design/lib/_core/_constants/assets.gen.dart b/design/lib/_core/_constants/assets.gen.dart
new file mode 100644
index 0000000..ff1339f
--- /dev/null
+++ b/design/lib/_core/_constants/assets.gen.dart
@@ -0,0 +1,14 @@
+/// GENERATED CODE - DO NOT MODIFY BY HAND
+/// *****************************************************
+/// FlutterGen
+/// *****************************************************
+
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
+
+class Assets {
+ Assets._();
+
+ static const String package = 'design';
+}
diff --git a/design/lib/_core/_constants/colours.dart b/design/lib/_core/_constants/colours.dart
new file mode 100644
index 0000000..4304ba3
--- /dev/null
+++ b/design/lib/_core/_constants/colours.dart
@@ -0,0 +1,75 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-correct-callback-field-name
+
+import 'package:flutter/material.dart';
+
+/// The `Colours` class provides a centralized collection of static color constants
+/// and getters that define the primary and accent color palette used across the
+/// application. These colors are consistent and reusable to ensure uniform design
+/// language across the app's UI components.
+final class Colours {
+ /// Pure white color, used typically for backgrounds and text.
+ static const Color kWhite = Color(0xFFFFFFFF);
+
+ /// Semi-grey color, a light grey used for subtle UI elements like borders.
+ static const Color kSemiGrey = Color(0xFFE2E4E5);
+
+ /// Half-grey color, a medium grey used for text or inactive UI elements.
+ static const Color kHalfGrey = Color(0xFF929292);
+
+ /// Full grey color, a darker grey suitable for primary text or more prominent elements.
+ static const Color kGrey = Color(0xFF494949);
+
+ /// Light blue color, used for softer accent elements in the UI.
+ static const Color kSemiBlue = Color(0xFFDBEEF9);
+
+ /// Bright blue color, often used for primary buttons and interactive elements.
+ static const Color kBlue = Color(0xFF69C9FF);
+
+ /// Light red color, used for background elements indicating warnings or errors.
+ static const Color kSemiRed = Color(0xFFEFD5D4);
+
+ /// Bright red color, commonly used for errors or alert messages.
+ static const Color kRed = Color(0xFFED7870);
+
+ /// Bright green color, suitable for success messages or confirmation buttons.
+ static const Color kGreen = Color(0xFF40DBA3);
+
+ /// Light green color, used for softer success indicators.
+ static const Color kSemiGreen = Color(0xFFC6EFE1);
+
+ /// Getter for white color.
+ Color get white => kWhite;
+
+ /// Getter for semi-grey color.
+ Color get semiGrey => kSemiGrey;
+
+ /// Getter for half-grey color.
+ Color get halfGrey => kHalfGrey;
+
+ /// Getter for grey color.
+ Color get grey => kGrey;
+
+ /// Getter for semi-blue color.
+ Color get semiBlue => kSemiBlue;
+
+ /// Getter for blue color.
+ Color get blue => kBlue;
+
+ /// Getter for semi-red color.
+ Color get semiRed => kSemiRed;
+
+ /// Getter for red color.
+ Color get red => kRed;
+
+ /// Getter for green color.
+ Color get green => kGreen;
+
+ /// Getter for semi-green color.
+ Color get semiGreen => kSemiGreen;
+}
diff --git a/design/lib/_core/_constants/defaults.dart b/design/lib/_core/_constants/defaults.dart
new file mode 100644
index 0000000..bf8934e
--- /dev/null
+++ b/design/lib/_core/_constants/defaults.dart
@@ -0,0 +1,27 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/widgets.dart';
+
+/// The `Defaults` class provides a collection of default styling properties
+/// that can be used throughout the application to ensure consistency in design.
+///
+/// This class primarily includes a default box shadow configuration which can
+/// be applied to various UI elements such as containers, cards, and buttons.
+final class Defaults {
+ /// Default box shadow styling for elevated UI components.
+ ///
+ /// - **color**: The shadow color is set to a dark gray.
+ /// - **offset**: The shadow is offset slightly downwards (0, 1).
+ /// - **blurRadius**: The blur radius is set to 1, giving it a subtle shadow effect.
+ final List boxShadow = [
+ const BoxShadow(
+ color: Color.fromARGB(255, 40, 40, 41),
+ offset: Offset(0, 1),
+ blurRadius: 1,
+ ),
+ ];
+}
diff --git a/design/lib/_core/_constants/fonts.dart b/design/lib/_core/_constants/fonts.dart
new file mode 100644
index 0000000..f732ab6
--- /dev/null
+++ b/design/lib/_core/_constants/fonts.dart
@@ -0,0 +1,125 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-correct-identifier-length
+
+import 'package:flutter/material.dart';
+
+/// The `Fonts` class provides predefined text styles that can be reused
+/// throughout the application to maintain consistency in typography.
+///
+/// Each text style is configured with the following attributes:
+/// - **color**: Text color, defaulting to a shade of gray.
+/// - **fontSize**: Font size in logical pixels.
+/// - **fontWeight**: The thickness of the font, ranging from normal to bold.
+/// - **fontStyle**: Regular or italicized text style.
+/// - **fontFamily**: Specifies the `Inter` font family for all text.
+
+final class Fonts {
+ /// Heading 1 (h1) text style.
+ ///
+ /// - Font Size: 24px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle h1 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 24,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Heading 2 (h2) text style.
+ ///
+ /// - Font Size: 18px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle h2 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 18,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Subtitle 1 (sub1) text style.
+ ///
+ /// - Font Size: 15px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle sub1 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 15,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Subtitle 2 (sub2) text style.
+ ///
+ /// - Font Size: 14px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle sub2 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Body Text 1 (body1) text style.
+ ///
+ /// - Font Size: 13px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle body1 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 13,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Body Text 2 (body2) text style.
+ ///
+ /// - Font Size: 11px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle body2 = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 11,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Button text style.
+ ///
+ /// - Font Size: 11px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle button = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 11,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+
+ /// Caption text style.
+ ///
+ /// - Font Size: 9px
+ /// - Font Weight: Normal
+ /// - Color: Dark Gray (#494949)
+ final TextStyle caption = const TextStyle(
+ color: Color(0xFF494949),
+ fontSize: 9,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ fontFamily: 'Inter',
+ );
+}
diff --git a/design/lib/_core/_constants/fonts.gen.dart b/design/lib/_core/_constants/fonts.gen.dart
new file mode 100644
index 0000000..84dd275
--- /dev/null
+++ b/design/lib/_core/_constants/fonts.gen.dart
@@ -0,0 +1,15 @@
+/// GENERATED CODE - DO NOT MODIFY BY HAND
+/// *****************************************************
+/// FlutterGen
+/// *****************************************************
+
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
+
+class FontFamily {
+ FontFamily._();
+
+ /// Font family: Inter
+ static const String inter = 'Inter';
+}
diff --git a/design/lib/_core/_constants/gradients.dart b/design/lib/_core/_constants/gradients.dart
new file mode 100644
index 0000000..9b6323b
--- /dev/null
+++ b/design/lib/_core/_constants/gradients.dart
@@ -0,0 +1,97 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-correct-callback-field-name
+
+import 'package:flutter/material.dart';
+
+/// The `Gradients` class provides a collection of predefined linear gradients
+/// for consistent use of gradient styles across the application.
+///
+/// Each gradient has two colors with a top-to-bottom alignment (vertical gradient).
+/// The available gradients include various shades of grey, red, green, and blue.
+
+final class Gradients {
+ /// Semi-transparent grey gradient from light grey to slightly darker grey.
+ static const LinearGradient kSemiGrey = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFFE2E4E5), Color(0xFFECEEEE)],
+ );
+
+ /// Dark grey gradient from a darker grey to a slightly lighter shade.
+ static const LinearGradient kGrey = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFF494949), Color(0xFF5D5D5D)],
+ );
+
+ /// Semi-transparent red gradient transitioning from light red to a soft peach.
+ static const LinearGradient kSemiRed = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFFFBDDDD), Color(0xFFFAE0DA)],
+ );
+
+ /// Bright red gradient transitioning from a bold red to a lighter red.
+ static const LinearGradient kRed = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFFED6E76), Color(0xFFEC816A)],
+ );
+
+ /// Semi-transparent green gradient transitioning from a light green to a soft teal.
+ static const LinearGradient kSemiGreen = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFFCFF6E8), Color(0xFFD4F7EB)],
+ );
+
+ /// Bright green gradient transitioning from a bold green to a lighter green.
+ static const LinearGradient kGreen = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFF40DBA3), Color(0xFF54DFAD)],
+ );
+
+ /// Semi-transparent blue gradient transitioning from light blue to a soft sky blue.
+ static const LinearGradient kSemiBlue = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFFDAF2FF), Color(0xFFDDF3FF)],
+ );
+
+ /// Bright blue gradient transitioning from a bold blue to a lighter blue.
+ static const LinearGradient kBlue = LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0xFF69C9FF), Color(0xFF78CFFF)],
+ );
+
+ /// Getter for the semi-transparent grey gradient.
+ LinearGradient get semiGrey => Gradients.kSemiGrey;
+
+ /// Getter for the dark grey gradient.
+ LinearGradient get grey => Gradients.kGrey;
+
+ /// Getter for the semi-transparent red gradient.
+ LinearGradient get semiRed => Gradients.kSemiRed;
+
+ /// Getter for the bright red gradient.
+ LinearGradient get red => Gradients.kRed;
+
+ /// Getter for the semi-transparent green gradient.
+ LinearGradient get semiGreen => Gradients.kSemiGreen;
+
+ /// Getter for the bright green gradient.
+ LinearGradient get green => Gradients.kGreen;
+
+ /// Getter for the semi-transparent blue gradient.
+ LinearGradient get semiBlue => Gradients.kSemiBlue;
+
+ /// Getter for the bright blue gradient.
+ LinearGradient get blue => Gradients.kBlue;
+}
diff --git a/design/lib/_core/_constants/theme.gen.dart b/design/lib/_core/_constants/theme.gen.dart
new file mode 100644
index 0000000..3a2fde3
--- /dev/null
+++ b/design/lib/_core/_constants/theme.gen.dart
@@ -0,0 +1,84 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: unused_import, avoid-global-state
+
+import 'package:design/_core/_constants/assets.gen.dart';
+import 'package:design/_core/_constants/colours.dart';
+import 'package:design/_core/_constants/defaults.dart';
+import 'package:design/_core/_constants/fonts.dart';
+import 'package:design/_core/_constants/gradients.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart' hide TextTheme;
+import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';
+
+part 'theme.gen.tailor.dart';
+
+/// The `ThemeGen` class is a customizable and extendable theme class that defines
+/// various theme-related properties for the application. It leverages Flutter's
+/// ThemeExtension to allow for theme configuration, custom properties, and diagnostic tools.
+///
+/// It uses the TailorMixin, provided by the `theme_tailor_annotation` package,
+/// to automatically generate custom-tailored themes. The class contains properties
+/// like colors, fonts, shadows, and gradients, all designed for easy reuse and
+/// customization across the application.
+@TailorMixin(themeGetter: ThemeGetter.onThemeData)
+class ThemeGen extends ThemeExtension
+ with DiagnosticableTreeMixin, _$ThemeGenTailorMixin {
+ /// The constructor for the `ThemeGen` class. It requires all theme properties to be passed.
+ const ThemeGen({
+ required this.background,
+ required this.surface,
+ required this.onSurface,
+ required this.border,
+ required this.shadow,
+ required this.placeholder,
+ required this.gradients,
+ required this.colors,
+ required this.fonts,
+ required this.defaults,
+ });
+
+ /// The background color used across the application.
+ @override
+ final Color background;
+
+ /// The color of the surface elements, such as cards or containers.
+ @override
+ final Color surface;
+
+ /// The color for text and icons on surface elements.
+ @override
+ final Color onSurface;
+
+ /// The color used for borders.
+ @override
+ final Color border;
+
+ /// The color used for shadows across the application.
+ @override
+ final Color shadow;
+
+ /// The color used for placeholders in inputs and other elements.
+ @override
+ final Color placeholder;
+
+ /// A collection of predefined linear gradients used throughout the app.
+ @override
+ final Gradients gradients;
+
+ /// A collection of colors defined in the `Colours` class.
+ @override
+ final Colours colors;
+
+ /// A collection of text styles defined in the `Fonts` class.
+ @override
+ final Fonts fonts;
+
+ /// Default configuration for shadows and other UI elements.
+ @override
+ final Defaults defaults;
+}
diff --git a/design/lib/_core/_constants/theme.gen.tailor.dart b/design/lib/_core/_constants/theme.gen.tailor.dart
new file mode 100644
index 0000000..8134ab0
--- /dev/null
+++ b/design/lib/_core/_constants/theme.gen.tailor.dart
@@ -0,0 +1,124 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint, unused_element, unnecessary_cast
+
+part of 'theme.gen.dart';
+
+// **************************************************************************
+// TailorAnnotationsGenerator
+// **************************************************************************
+
+mixin _$ThemeGenTailorMixin
+ on ThemeExtension, DiagnosticableTreeMixin {
+ Color get background;
+ Color get surface;
+ Color get onSurface;
+ Color get border;
+ Color get shadow;
+ Color get placeholder;
+ Gradients get gradients;
+ Colours get colors;
+ Fonts get fonts;
+ Defaults get defaults;
+
+ @override
+ ThemeGen copyWith({
+ Color? background,
+ Color? surface,
+ Color? onSurface,
+ Color? border,
+ Color? shadow,
+ Color? placeholder,
+ Gradients? gradients,
+ Colours? colors,
+ Fonts? fonts,
+ Defaults? defaults,
+ }) {
+ return ThemeGen(
+ background: background ?? this.background,
+ surface: surface ?? this.surface,
+ onSurface: onSurface ?? this.onSurface,
+ border: border ?? this.border,
+ shadow: shadow ?? this.shadow,
+ placeholder: placeholder ?? this.placeholder,
+ gradients: gradients ?? this.gradients,
+ colors: colors ?? this.colors,
+ fonts: fonts ?? this.fonts,
+ defaults: defaults ?? this.defaults,
+ );
+ }
+
+ @override
+ ThemeGen lerp(covariant ThemeExtension? other, double t) {
+ if (other is! ThemeGen) return this as ThemeGen;
+ return ThemeGen(
+ background: Color.lerp(background, other.background, t)!,
+ surface: Color.lerp(surface, other.surface, t)!,
+ onSurface: Color.lerp(onSurface, other.onSurface, t)!,
+ border: Color.lerp(border, other.border, t)!,
+ shadow: Color.lerp(shadow, other.shadow, t)!,
+ placeholder: Color.lerp(placeholder, other.placeholder, t)!,
+ gradients: t < 0.5 ? gradients : other.gradients,
+ colors: t < 0.5 ? colors : other.colors,
+ fonts: t < 0.5 ? fonts : other.fonts,
+ defaults: t < 0.5 ? defaults : other.defaults,
+ );
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is ThemeGen &&
+ const DeepCollectionEquality()
+ .equals(background, other.background) &&
+ const DeepCollectionEquality().equals(surface, other.surface) &&
+ const DeepCollectionEquality().equals(onSurface, other.onSurface) &&
+ const DeepCollectionEquality().equals(border, other.border) &&
+ const DeepCollectionEquality().equals(shadow, other.shadow) &&
+ const DeepCollectionEquality()
+ .equals(placeholder, other.placeholder) &&
+ const DeepCollectionEquality().equals(gradients, other.gradients) &&
+ const DeepCollectionEquality().equals(colors, other.colors) &&
+ const DeepCollectionEquality().equals(fonts, other.fonts) &&
+ const DeepCollectionEquality().equals(defaults, other.defaults));
+ }
+
+ @override
+ int get hashCode {
+ return Object.hash(
+ runtimeType.hashCode,
+ const DeepCollectionEquality().hash(background),
+ const DeepCollectionEquality().hash(surface),
+ const DeepCollectionEquality().hash(onSurface),
+ const DeepCollectionEquality().hash(border),
+ const DeepCollectionEquality().hash(shadow),
+ const DeepCollectionEquality().hash(placeholder),
+ const DeepCollectionEquality().hash(gradients),
+ const DeepCollectionEquality().hash(colors),
+ const DeepCollectionEquality().hash(fonts),
+ const DeepCollectionEquality().hash(defaults),
+ );
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(DiagnosticsProperty('type', 'ThemeGen'))
+ ..add(DiagnosticsProperty('background', background))
+ ..add(DiagnosticsProperty('surface', surface))
+ ..add(DiagnosticsProperty('onSurface', onSurface))
+ ..add(DiagnosticsProperty('border', border))
+ ..add(DiagnosticsProperty('shadow', shadow))
+ ..add(DiagnosticsProperty('placeholder', placeholder))
+ ..add(DiagnosticsProperty('gradients', gradients))
+ ..add(DiagnosticsProperty('colors', colors))
+ ..add(DiagnosticsProperty('fonts', fonts))
+ ..add(DiagnosticsProperty('defaults', defaults));
+ }
+}
+
+extension ThemeGenThemeData on ThemeData {
+ ThemeGen get themeGen => extension()!;
+}
diff --git a/design/lib/_core/_constants/themes.dart b/design/lib/_core/_constants/themes.dart
new file mode 100644
index 0000000..261912a
--- /dev/null
+++ b/design/lib/_core/_constants/themes.dart
@@ -0,0 +1,69 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: no-equal-arguments, avoid-non-null-assertion, arguments-ordering
+
+import 'package:design/_core/_constants/colours.dart';
+import 'package:design/_core/_constants/defaults.dart';
+import 'package:design/_core/_constants/fonts.dart';
+import 'package:design/_core/_constants/gradients.dart';
+import 'package:design/_core/_constants/theme.gen.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+/// The `Themes` class defines the theme configuration for the application.
+/// It provides a static theme, which is customizable, and uses various
+/// design constants such as colors, fonts, gradients, and other UI elements.
+///
+/// This class abstracts away theme configuration, making it easy to apply
+/// consistent styles across the application by just referencing the predefined theme.
+abstract final class Themes {
+ /// The `light` theme configuration provides a light color scheme for the application.
+ /// It uses the Material and Cupertino design languages, allowing the application
+ /// to have a uniform appearance across platforms.
+ ///
+ /// Key properties include:
+ /// - **cupertinoOverrideTheme**: Defines theme settings specific to Cupertino (iOS) widgets.
+ /// - **extensions**: Uses `ThemeGen` to extend and include custom theme properties such as gradients, fonts, and colors.
+ /// - **textSelectionTheme**: Configures the appearance of text selection, including cursor and selection colors.
+ /// - **brightness**: Defines the overall brightness of the theme (light).
+ /// - **scaffoldBackgroundColor**: Sets the background color for the scaffold (main application background).
+ static final ThemeData light = ThemeData(
+ cupertinoOverrideTheme: const CupertinoThemeData(
+ brightness: Brightness.light,
+ primaryColor: Color(0xFF69C9FF),
+ barBackgroundColor: Color(0xFFF3F5F6),
+ primaryContrastingColor: Color(0xFFF3F5F6),
+ scaffoldBackgroundColor: Color(0xFFF3F5F6),
+ ),
+
+ // Extend the theme with custom properties defined in ThemeGen.
+ extensions: >[
+ ThemeGen(
+ background: const Color(0xFFF3F5F6),
+ surface: const Color(0xFFFFFFFF),
+ onSurface: const Color(0xFFF3F5F6),
+ border: const Color(0xFFECECEC),
+ shadow: const Color(0xFFDBDBDB),
+ placeholder: const Color(0xFFB6B6B6),
+ gradients: Gradients(),
+ colors: Colours(),
+ fonts: Fonts(),
+ defaults: Defaults(),
+ ),
+ ],
+
+ // Define the text selection theme including cursor and selection colors.
+ textSelectionTheme: const TextSelectionThemeData(
+ cursorColor: Color(0xFF69C9FF),
+ selectionColor: Color(0xFF69C9FF),
+ selectionHandleColor: Color(0xFF69C9FF),
+ ),
+
+ brightness: Brightness.light,
+ scaffoldBackgroundColor: const Color(0xFFF3F5F6),
+ );
+}
diff --git a/design/lib/_core/_i18n/design_i18n_cubit_locale.ext.dart b/design/lib/_core/_i18n/design_i18n_cubit_locale.ext.dart
new file mode 100644
index 0000000..053be7f
--- /dev/null
+++ b/design/lib/_core/_i18n/design_i18n_cubit_locale.ext.dart
@@ -0,0 +1,28 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:design/_core/_i18n/translations.g.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `Cubit` to easily access the design's i18n (internationalization) functionality.
+///
+/// This extension provides a convenient way to retrieve the appropriate `Translations` object for the current locale
+/// managed by a `Cubit`. It uses `AppLocaleUtils.parse` to map the `Locale` to a specific `AppLocale` and
+/// then builds the `Translations` object, which contains localized strings for the app.
+///
+/// Example usage:
+/// ```dart
+/// final translations = $.tr.design;
+/// ```
+extension DesignI18nCubitLocaleExt on Cubit {
+ /// Retrieves the `Translations` object for the current locale.
+ ///
+ /// This method maps the `Locale` stored in the cubit's state to an `AppLocale` using `AppLocaleUtils.parse`.
+ /// It then builds and returns the corresponding `Translations` object, which provides localized strings
+ /// for use in the application.
+ Translations get design => AppLocaleUtils.parse(state.toString()).build();
+}
diff --git a/design/lib/_core/_i18n/strings_en.i18n.json b/design/lib/_core/_i18n/strings_en.i18n.json
new file mode 100644
index 0000000..0bb6739
--- /dev/null
+++ b/design/lib/_core/_i18n/strings_en.i18n.json
@@ -0,0 +1,5 @@
+{
+ "atoms": {},
+ "widgets": {},
+ "core": {}
+}
\ No newline at end of file
diff --git a/design/lib/_core/_i18n/strings_tr.i18n.json b/design/lib/_core/_i18n/strings_tr.i18n.json
new file mode 100644
index 0000000..0bb6739
--- /dev/null
+++ b/design/lib/_core/_i18n/strings_tr.i18n.json
@@ -0,0 +1,5 @@
+{
+ "atoms": {},
+ "widgets": {},
+ "core": {}
+}
\ No newline at end of file
diff --git a/design/lib/_core/extensions/dialog_context.ext.dart b/design/lib/_core/extensions/dialog_context.ext.dart
new file mode 100644
index 0000000..65231d7
--- /dev/null
+++ b/design/lib/_core/extensions/dialog_context.ext.dart
@@ -0,0 +1,51 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `DialogContext` to provide additional convenience methods for displaying dialogs, modals, and sheets.
+///
+/// This extension provides simplified methods to push dialogs, modals, and bottom sheets within a Flutter application,
+/// with customizable dismissibility options.
+extension DialogContextExt on DialogContext {
+ /// Shows a custom dialog in the current context.
+ ///
+ /// - `dialog`: The widget to be shown as the dialog.
+ /// - `isDismissible`: Specifies whether the dialog can be dismissed by tapping outside of it. Default is `false`.
+ ///
+ /// Returns a `Future` that completes when the dialog is dismissed.
+ Future showDialog(Widget dialog, {bool isDismissible = false}) async {
+ return $.dialog.pushDialog(
+ builder: (_) => dialog,
+ config: DialogConfig(isBarrierDismissible: isDismissible),
+ );
+ }
+
+ /// Shows a modal dialog in the current context.
+ ///
+ /// - `modal`: The widget to be shown as the modal.
+ ///
+ /// Returns a `Future` that completes when the modal is dismissed.
+ Future showModal(Widget modal) async {
+ return $.dialog.pushModal(
+ builder: (_) => modal,
+ );
+ }
+
+ /// Displays a bottom sheet in the current context.
+ ///
+ /// - `sheet`: The widget to be shown as the bottom sheet.
+ /// - `isDismissible`: Specifies whether the sheet can be dismissed by dragging it down. Default is `false`.
+ ///
+ /// Does not return a value as sheets generally remain open until explicitly dismissed.
+ void showSheet(Widget sheet, {bool isDismissible = false}) {
+ $.dialog.pushSheet(
+ builder: (_) => sheet,
+ config: SheetConfig(shouldEnableDrag: isDismissible),
+ );
+ }
+}
diff --git a/design/lib/_core/extensions/overlay_context.ext.dart b/design/lib/_core/extensions/overlay_context.ext.dart
new file mode 100644
index 0000000..359bbd6
--- /dev/null
+++ b/design/lib/_core/extensions/overlay_context.ext.dart
@@ -0,0 +1,37 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `OverlayContext` to provide an easy method for displaying a loading overlay.
+///
+/// This extension allows you to show a loading spinner with a semi-transparent background, preventing user interaction
+/// with the underlying UI while loading is in progress.
+extension OverlayContextExt on OverlayContext {
+ /// Displays a loading overlay that blocks user interaction.
+ ///
+ /// The overlay consists of:
+ /// - A `ModalBarrier` that covers the entire screen with a semi-transparent background, preventing user interaction.
+ /// - `CircularProgressIndicator` widget that shows a loading spinner.
+ ///
+ /// The `popOverlay` method is used to dismiss the overlay once the loading is complete.
+ Future showLoading() async {
+ await pushOverlay(
+ builder: (_) => Stack(
+ children: [
+ ModalBarrier(
+ color: Colors.black26,
+ dismissible: false,
+ onDismiss: popOverlay,
+ ),
+ const Center(
+ child: CircularProgressIndicator.adaptive(strokeWidth: 10)),
+ ],
+ ),
+ );
+ }
+}
diff --git a/design/lib/_core/extensions/theme_text_style.ext.dart b/design/lib/_core/extensions/theme_text_style.ext.dart
new file mode 100644
index 0000000..6f68229
--- /dev/null
+++ b/design/lib/_core/extensions/theme_text_style.ext.dart
@@ -0,0 +1,22 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `TextStyle` to simplify applying predefined theme colors to text styles.
+///
+/// This extension allows easy customization of text color using predefined theme colors, making it easier to manage
+/// consistent color usage across the app.
+extension ThemeTextStyleExt on TextStyle {
+ /// Applies the white color from the theme to the text style.
+ ///
+ /// Example:
+ /// ```dart
+ /// TextStyle().white; // Returns a TextStyle with the color set to white.
+ /// ```
+ TextStyle get white => copyWith(color: $.theme.colors.white);
+}
diff --git a/design/lib/_core/extensions/toast_context.ext.dart b/design/lib/_core/extensions/toast_context.ext.dart
new file mode 100644
index 0000000..174fb97
--- /dev/null
+++ b/design/lib/_core/extensions/toast_context.ext.dart
@@ -0,0 +1,65 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:flutter/cupertino.dart';
+
+/// Extension on `ToastContext` for displaying alerts based on `Failure` objects.
+///
+/// This extension provides an asynchronous method for showing alerts depending on the type of failure,
+/// either constructive (e.g., success messages) or error-based alerts. It leverages toast notifications to provide
+/// feedback to the user based on the failure type.
+extension ToastContextExt on ToastContext {
+ /// Asynchronously displays an alert based on the `Failure` type using the toast system.
+ ///
+ /// This method first removes any existing overlays, then checks the `Failure` type. If the failure
+ /// type is [FailureTypeEnum.empty], no alert will be shown. Otherwise, it displays a toast
+ /// with a colored [Container] and the failure message.
+ ///
+ /// Example usage:
+ /// ```dart
+ /// final Failure failure = Failure(type: FailureTypeEnum.error, message: 'An error occurred');
+ /// await $.toast.showAlert(failure);
+ /// ```
+ ///
+ /// - [failure]: The failure object containing the failure type and message.
+ Future showAlert(
+ Failure failure, {
+ bool shouldAutoDismissModal = false,
+ }) async {
+ // Remove any existing overlay before showing a new alert.
+ await $.overlay.popOverlay();
+
+ // Remove any existing modal before showing a new alert.
+ if (shouldAutoDismissModal) {
+ await $.dialog.popDialog();
+ }
+
+ // If the failure type is empty, do nothing.
+ if (failure.type == FailureTypeEnum.empty) {
+ return;
+ }
+
+ // Create a widget based on the failure type. Success is green, and errors are red.
+ final Widget child = switch (failure.type) {
+ // Constructive (e.g., success) alerts show a green background.
+ FailureTypeEnum.constructive => Container(
+ height: 50,
+ color: const Color(0xFF40DBA3),
+ child: failure.message.text(),
+ ),
+ // Error alerts show a red background.
+ _ => Container(
+ height: 50,
+ color: const Color(0xFFE4756D),
+ child: failure.message.text(),
+ ),
+ };
+
+ // Display the alert as a toast with the generated widget.
+ await $.toast.pushWidgetToast(child: child);
+ }
+}
diff --git a/design/lib/design.dart b/design/lib/design.dart
new file mode 100644
index 0000000..e7b38ad
--- /dev/null
+++ b/design/lib/design.dart
@@ -0,0 +1,8 @@
+export '_core/_constants/assets.gen.dart';
+export '_core/_constants/theme.gen.dart';
+export '_core/_constants/themes.dart';
+export '_core/_i18n/design_i18n_cubit_locale.ext.dart';
+export '_core/extensions/dialog_context.ext.dart';
+export '_core/extensions/overlay_context.ext.dart';
+export '_core/extensions/theme_text_style.ext.dart';
+export '_core/extensions/toast_context.ext.dart';
diff --git a/design/pubspec.yaml b/design/pubspec.yaml
new file mode 100644
index 0000000..556121b
--- /dev/null
+++ b/design/pubspec.yaml
@@ -0,0 +1,63 @@
+name: design
+description: The design system of the flutter advanced boilerplate.
+publish_to: none
+version: 0.0.1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ deps:
+ path: ../deps
+ flutter:
+ sdk: flutter
+ flutter_svg: ^2.0.10+1
+ theme_tailor_annotation: ^3.0.1
+
+dev_dependencies:
+ build_runner: ^2.4.13
+ dart_code_metrics_presets: ^2.16.0
+ flutter_gen_runner: ^5.7.0
+ json_serializable: ^6.8.0
+ slang_build_runner: ^3.31.0
+ theme_tailor: ^3.0.1
+
+flutter:
+ uses-material-design: true
+ assets:
+ - assets/icons/
+ - assets/anims/
+ - assets/images/
+
+ fonts:
+ - family: Inter
+ fonts:
+ - asset: assets/fonts/Inter/Inter-Black.ttf
+ weight: 900
+ - asset: assets/fonts/Inter/Inter-ExtraBold.ttf
+ weight: 800
+ - asset: assets/fonts/Inter/Inter-Bold.ttf
+ weight: 700
+ - asset: assets/fonts/Inter/Inter-SemiBold.ttf
+ weight: 600
+ - asset: assets/fonts/Inter/Inter-Medium.ttf
+ weight: 500
+ - asset: assets/fonts/Inter/Inter-Regular.ttf
+ weight: 400
+ - asset: assets/fonts/Inter/Inter-Light.ttf
+ weight: 300
+ - asset: assets/fonts/Inter/Inter-ExtraLight.ttf
+ weight: 200
+ - asset: assets/fonts/Inter/Inter-Thin.ttf
+ weight: 100
+
+
+flutter_gen:
+ output: lib/_core/_constants/
+ line_length: 120
+ integrations:
+ flutter_svg: true
+ lottie: true
+ assets:
+ outputs:
+ package_parameter_enabled: true
\ No newline at end of file
diff --git a/design/pubspec_overrides.yaml b/design/pubspec_overrides.yaml
new file mode 100644
index 0000000..c884732
--- /dev/null
+++ b/design/pubspec_overrides.yaml
@@ -0,0 +1,12 @@
+# melos_managed_dependency_overrides: deps,feature_auth,feature_core,feature_user,infrastructure
+dependency_overrides:
+ deps:
+ path: ../deps
+ feature_auth:
+ path: ../features/auth
+ feature_core:
+ path: ../features/_core
+ feature_user:
+ path: ../features/user
+ infrastructure:
+ path: ../infrastructure
diff --git a/docs.json b/docs.json
new file mode 100644
index 0000000..2e90b43
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,281 @@
+{
+ "anchors": [
+ {
+ "href": "#",
+ "icon": "hand-holding-dollar",
+ "title": "Have no time or want more?"
+ },
+ {
+ "href": "/design",
+ "icon": "figma",
+ "title": "Looking for a ready-to-use kit?"
+ },
+ {
+ "href": "#",
+ "icon": "discord",
+ "title": "Need help or a new feature?"
+ }
+ ],
+ "description": "",
+ "logo": {
+ "href": "/",
+ "light": "/assets/logo.svg"
+ },
+ "name": "Flutter Advanced Boilerplate",
+ "sidebar": [
+ {
+ "group": "Getting Started",
+ "pages": [
+ {
+ "href": "/",
+ "icon": "readme",
+ "title": "About"
+ },
+ {
+ "href": "/getting-started/dependencies",
+ "icon": "circle-nodes",
+ "title": "Dependencies"
+ },
+ {
+ "href": "/getting-started/architecture",
+ "icon": "folder-tree",
+ "title": "Architecture"
+ },
+ {
+ "href": "/getting-started/changelog",
+ "icon": "arrow-up-9-1",
+ "title": "Changelog"
+ },
+ {
+ "href": "/getting-started/license",
+ "icon": "scale-balanced",
+ "title": "License"
+ }
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ {
+ "group": "Preparation",
+ "icon": "screwdriver-wrench",
+ "pages": [
+ {
+ "href": "/guides/preparation/set-the-environment",
+ "icon": "download",
+ "title": "Set The Environment"
+ },
+ {
+ "href": "/guides/preparation/install-extensions",
+ "icon": "puzzle-piece",
+ "title": "Install Extensions"
+ },
+ {
+ "href": "/guides/preparation/configure-snippets",
+ "icon": "code",
+ "title": "Configure Snippets"
+ }
+ ]
+ },
+ {
+ "group": "Setup",
+ "icon": "hammer",
+ "pages": [
+ {
+ "href": "/guides/setup/fetch-and-int",
+ "icon": "download",
+ "title": "Fetch and Init"
+ },
+ {
+ "href": "/guides/setup/understand-melos",
+ "icon": "m",
+ "title": "Understand Melos"
+ },
+ {
+ "href": "/guides/setup/generate-code",
+ "icon": "code",
+ "title": "Generate Code"
+ }
+ ]
+ },
+ {
+ "href": "/guides/startup",
+ "icon": "terminal",
+ "title": "Startup"
+ },
+ {
+ "group": "Deployment",
+ "icon": "rocket",
+ "pages": [
+ {
+ "group": "Pre-deployment",
+ "icon": "toolbox",
+ "pages": [
+ {
+ "href": "/guides/deployment/pre-deployment/change-app-icon",
+ "icon": "shapes",
+ "title": "Change App Icon"
+ },
+ {
+ "href": "/guides/deployment/pre-deployment/generate-splash-screen",
+ "icon": "mobile-screen",
+ "title": "Generate Splash Screen"
+ }
+ ]
+ },
+ {
+ "href": "/guides/deployment/versioning",
+ "icon": "arrows-split-up-and-left",
+ "title": "Versioning"
+ },
+ {
+ "href": "/guides/deployment/publish-google-play-store",
+ "icon": "google-play",
+ "title": "Publish Google Play Store"
+ },
+ {
+ "href": "/guides/deployment/publish-apple-store",
+ "icon": "app-store",
+ "title": "Publish Apple App Store"
+ }
+ ]
+ },
+ {
+ "href": "/guides/automation-ci-cd",
+ "icon": "robot",
+ "title": "Automation (CI/CD)"
+ }
+ ]
+ },
+ {
+ "group": "Modules",
+ "pages": [
+ {
+ "href": "/modules/app",
+ "icon": "hashtag",
+ "title": "app"
+ },
+ {
+ "group": "features",
+ "icon": "icons",
+ "pages": [
+ {
+ "href": "/modules/features/_core",
+ "icon": "asterisk",
+ "title": "_core"
+ },
+ {
+ "href": "/modules/features/feature-x",
+ "icon": "florin-sign",
+ "title": "feature_x"
+ }
+ ]
+ },
+ {
+ "href": "/modules/deps",
+ "icon": "bezier-curve",
+ "title": "deps"
+ },
+ {
+ "group": "design",
+ "icon": "palette",
+ "pages": [
+ {
+ "href": "/modules/design/_core",
+ "icon": "asterisk",
+ "title": "_core"
+ },
+ {
+ "href": "/modules/design/atoms",
+ "icon": "border-top-left",
+ "title": "atoms"
+ },
+ {
+ "href": "/modules/design/widgets",
+ "icon": "border-top-left",
+ "title": "widgets"
+ }
+ ]
+ },
+
+
+ {
+ "group": "infrastructure",
+ "icon": "layer-group",
+ "pages": [
+ {
+ "href": "/modules/infrastructure/_core",
+ "icon": "asterisk",
+ "title": "_core"
+ },
+ {
+ "href": "/modules/infrastructure/analytics",
+ "icon": "bug",
+ "title": "analytics"
+ },
+ {
+ "href": "/modules/infrastructure/flavors",
+ "icon": "at",
+ "title": "flavors"
+ },
+ {
+ "href": "/modules/infrastructure/networking",
+ "icon": "earth-asia",
+ "title": "networking"
+ },
+ {
+ "href": "/modules/infrastructure/permissions",
+ "icon": "unlock",
+ "title": "permissions"
+ },
+ {
+ "href": "/modules/infrastructure/presentation",
+ "icon": "cubes",
+ "title": "presentation"
+ },
+ {
+ "href": "/modules/infrastructure/storage",
+ "icon": "database",
+ "title": "storage"
+ },
+ {
+ "href": "/modules/infrastructure/translations",
+ "icon": "language",
+ "title": "translations"
+ }
+ ]
+ },
+ {
+ "href": "/modules/widgetbook",
+ "icon": "border-top-left",
+ "title": "widgetbook"
+ }
+ ]
+ },
+ {
+ "group": "Misc.",
+ "pages": [
+ {
+ "href": "/misc/faqs",
+ "icon": "circle-question",
+ "title": "FAQs"
+ }
+ ]
+ }
+ ],
+ "tabs": [
+ {
+ "href": "/",
+ "id": "documentation",
+ "title": "Documentation"
+ },
+ {
+ "href": "#",
+ "id": "cookbook",
+ "title": "Cookbook"
+ }
+ ],
+ "theme": {
+ "primary": "#027DFD"
+ }
+}
\ No newline at end of file
diff --git a/docs/assets/architecture.svg b/docs/assets/architecture.svg
new file mode 100644
index 0000000..cffbfb7
--- /dev/null
+++ b/docs/assets/architecture.svg
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg
new file mode 100644
index 0000000..cb98212
--- /dev/null
+++ b/docs/assets/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/getting-started/architecture.mdx b/docs/getting-started/architecture.mdx
new file mode 100644
index 0000000..0ebbf13
--- /dev/null
+++ b/docs/getting-started/architecture.mdx
@@ -0,0 +1,8 @@
+---
+title: Architecture
+description: Description
+---
+
+![Represantation of the architecture](/assets/architecture.svg)
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/getting-started/changelog.mdx b/docs/getting-started/changelog.mdx
new file mode 100644
index 0000000..358db04
--- /dev/null
+++ b/docs/getting-started/changelog.mdx
@@ -0,0 +1,6 @@
+---
+title: Changelog
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/getting-started/dependencies.mdx b/docs/getting-started/dependencies.mdx
new file mode 100644
index 0000000..e8ad7fb
--- /dev/null
+++ b/docs/getting-started/dependencies.mdx
@@ -0,0 +1,6 @@
+---
+title: Dependencies
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/getting-started/license.mdx b/docs/getting-started/license.mdx
new file mode 100644
index 0000000..d3a9c2a
--- /dev/null
+++ b/docs/getting-started/license.mdx
@@ -0,0 +1,33 @@
+---
+title: License
+description: BSD 4-Clause "Original" or "Old" License
+---
+
+A permissive license similar to the BSD 3-Clause License, but with an "advertising clause" that requires an acknowledgment of the original source in all advertising material.
+
+
+
+ Commercial use, Modification, Distribution, Private use
+
+
+ Liability, Warranty
+
+
+ License and copyright notice
+
+
+
+Copyright 2024 Fikret Şengül. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. All advertising materials mentioning features or use of this software must display the following acknowledgement:
+This product includes software developed by the organization .
+
+1. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/docs/guides/automation-ci-cd.mdx b/docs/guides/automation-ci-cd.mdx
new file mode 100644
index 0000000..96f2ad0
--- /dev/null
+++ b/docs/guides/automation-ci-cd.mdx
@@ -0,0 +1,6 @@
+---
+title: Automation (CI/CD)
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/deployment/pre-deployment/change-app-icon.mdx b/docs/guides/deployment/pre-deployment/change-app-icon.mdx
new file mode 100644
index 0000000..1bee269
--- /dev/null
+++ b/docs/guides/deployment/pre-deployment/change-app-icon.mdx
@@ -0,0 +1,6 @@
+---
+title: Change App Icon
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/deployment/pre-deployment/generate-splash-screen.mdx b/docs/guides/deployment/pre-deployment/generate-splash-screen.mdx
new file mode 100644
index 0000000..efec962
--- /dev/null
+++ b/docs/guides/deployment/pre-deployment/generate-splash-screen.mdx
@@ -0,0 +1,6 @@
+---
+title: Generate Splash Screen
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/deployment/publish-apple-store.mdx b/docs/guides/deployment/publish-apple-store.mdx
new file mode 100644
index 0000000..eebeffb
--- /dev/null
+++ b/docs/guides/deployment/publish-apple-store.mdx
@@ -0,0 +1,6 @@
+---
+title: Publish Apple App Store
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/deployment/publish-google-play-store.mdx b/docs/guides/deployment/publish-google-play-store.mdx
new file mode 100644
index 0000000..fb2244a
--- /dev/null
+++ b/docs/guides/deployment/publish-google-play-store.mdx
@@ -0,0 +1,6 @@
+---
+title: Publish Google Play Store
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/deployment/versioning.mdx b/docs/guides/deployment/versioning.mdx
new file mode 100644
index 0000000..828fb64
--- /dev/null
+++ b/docs/guides/deployment/versioning.mdx
@@ -0,0 +1,6 @@
+---
+title: Versioning
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/preparation/configure-snippets.mdx b/docs/guides/preparation/configure-snippets.mdx
new file mode 100644
index 0000000..589192e
--- /dev/null
+++ b/docs/guides/preparation/configure-snippets.mdx
@@ -0,0 +1,6 @@
+---
+title: Configure Snippets
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/preparation/install-extensions.mdx b/docs/guides/preparation/install-extensions.mdx
new file mode 100644
index 0000000..9a44611
--- /dev/null
+++ b/docs/guides/preparation/install-extensions.mdx
@@ -0,0 +1,6 @@
+---
+title: Install Extensions
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/preparation/set-the-environment.mdx b/docs/guides/preparation/set-the-environment.mdx
new file mode 100644
index 0000000..8fef0da
--- /dev/null
+++ b/docs/guides/preparation/set-the-environment.mdx
@@ -0,0 +1,6 @@
+---
+title: Set The Environment
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/setup/fetch-and-init.mdx b/docs/guides/setup/fetch-and-init.mdx
new file mode 100644
index 0000000..0793ea7
--- /dev/null
+++ b/docs/guides/setup/fetch-and-init.mdx
@@ -0,0 +1,6 @@
+---
+title: Fetch and Init
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/setup/generate-code.mdx b/docs/guides/setup/generate-code.mdx
new file mode 100644
index 0000000..a401012
--- /dev/null
+++ b/docs/guides/setup/generate-code.mdx
@@ -0,0 +1,6 @@
+---
+title: Generate Code
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/setup/understand-melos.mdx b/docs/guides/setup/understand-melos.mdx
new file mode 100644
index 0000000..4dc57ed
--- /dev/null
+++ b/docs/guides/setup/understand-melos.mdx
@@ -0,0 +1,6 @@
+---
+title: Understand Melos
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/guides/startup.mdx b/docs/guides/startup.mdx
new file mode 100644
index 0000000..87ee3c6
--- /dev/null
+++ b/docs/guides/startup.mdx
@@ -0,0 +1,6 @@
+---
+title: Startup
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/index.mdx b/docs/index.mdx
new file mode 100644
index 0000000..7b64891
--- /dev/null
+++ b/docs/index.mdx
@@ -0,0 +1,6 @@
+---
+title: About
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/misc/faqs.mdx b/docs/misc/faqs.mdx
new file mode 100644
index 0000000..7f031a8
--- /dev/null
+++ b/docs/misc/faqs.mdx
@@ -0,0 +1,6 @@
+---
+title: FAQs
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/app.mdx b/docs/modules/app.mdx
new file mode 100644
index 0000000..304a4f7
--- /dev/null
+++ b/docs/modules/app.mdx
@@ -0,0 +1,6 @@
+---
+title: App
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/deps.mdx b/docs/modules/deps.mdx
new file mode 100644
index 0000000..9f3a704
--- /dev/null
+++ b/docs/modules/deps.mdx
@@ -0,0 +1,6 @@
+---
+title: Deps
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/design/_core.mdx b/docs/modules/design/_core.mdx
new file mode 100644
index 0000000..2c43f21
--- /dev/null
+++ b/docs/modules/design/_core.mdx
@@ -0,0 +1,6 @@
+---
+title: Core
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/design/atoms.mdx b/docs/modules/design/atoms.mdx
new file mode 100644
index 0000000..3d64a68
--- /dev/null
+++ b/docs/modules/design/atoms.mdx
@@ -0,0 +1,6 @@
+---
+title: Atoms
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/design/widgets.mdx b/docs/modules/design/widgets.mdx
new file mode 100644
index 0000000..b818d34
--- /dev/null
+++ b/docs/modules/design/widgets.mdx
@@ -0,0 +1,6 @@
+---
+title: Widgets
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/features/_core.mdx b/docs/modules/features/_core.mdx
new file mode 100644
index 0000000..2c43f21
--- /dev/null
+++ b/docs/modules/features/_core.mdx
@@ -0,0 +1,6 @@
+---
+title: Core
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/features/feature-x.mdx b/docs/modules/features/feature-x.mdx
new file mode 100644
index 0000000..8587c39
--- /dev/null
+++ b/docs/modules/features/feature-x.mdx
@@ -0,0 +1,6 @@
+---
+title: Feature X
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/_core.mdx b/docs/modules/infrastructure/_core.mdx
new file mode 100644
index 0000000..2c43f21
--- /dev/null
+++ b/docs/modules/infrastructure/_core.mdx
@@ -0,0 +1,6 @@
+---
+title: Core
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/analytics.mdx b/docs/modules/infrastructure/analytics.mdx
new file mode 100644
index 0000000..9312cfa
--- /dev/null
+++ b/docs/modules/infrastructure/analytics.mdx
@@ -0,0 +1,6 @@
+---
+title: Analytics
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/flavors.mdx b/docs/modules/infrastructure/flavors.mdx
new file mode 100644
index 0000000..78af5f8
--- /dev/null
+++ b/docs/modules/infrastructure/flavors.mdx
@@ -0,0 +1,6 @@
+---
+title: Flavors
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/networking.mdx b/docs/modules/infrastructure/networking.mdx
new file mode 100644
index 0000000..b1b7a0b
--- /dev/null
+++ b/docs/modules/infrastructure/networking.mdx
@@ -0,0 +1,6 @@
+---
+title: Networking
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/permissions.mdx b/docs/modules/infrastructure/permissions.mdx
new file mode 100644
index 0000000..c867661
--- /dev/null
+++ b/docs/modules/infrastructure/permissions.mdx
@@ -0,0 +1,6 @@
+---
+title: Permissions
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/presentation.mdx b/docs/modules/infrastructure/presentation.mdx
new file mode 100644
index 0000000..04ade99
--- /dev/null
+++ b/docs/modules/infrastructure/presentation.mdx
@@ -0,0 +1,6 @@
+---
+title: Presentation
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/storage.mdx b/docs/modules/infrastructure/storage.mdx
new file mode 100644
index 0000000..090b123
--- /dev/null
+++ b/docs/modules/infrastructure/storage.mdx
@@ -0,0 +1,6 @@
+---
+title: Storage
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/infrastructure/translations.mdx b/docs/modules/infrastructure/translations.mdx
new file mode 100644
index 0000000..f075b99
--- /dev/null
+++ b/docs/modules/infrastructure/translations.mdx
@@ -0,0 +1,6 @@
+---
+title: Translations
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/docs/modules/widgetbook.mdx b/docs/modules/widgetbook.mdx
new file mode 100644
index 0000000..a4d04b1
--- /dev/null
+++ b/docs/modules/widgetbook.mdx
@@ -0,0 +1,6 @@
+---
+title: Widgetbook
+description: Description
+---
+
+This part is currently being documented. Thank you for your patience.
\ No newline at end of file
diff --git a/features/_core/build.yaml b/features/_core/build.yaml
new file mode 100644
index 0000000..d32e32f
--- /dev/null
+++ b/features/_core/build.yaml
@@ -0,0 +1,109 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+targets:
+ $default:
+ builders:
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_router_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/_router/*.router.dart
+
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_route_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/commons/pages/*.page.dart
+ - lib/commons/routers/*.router.dart
+
+ # Freezed Generators
+ #
+ # Freezed is used to generate immutable data classes, unions, and sealed classes. This section configures how
+ # Freezed generates code, particularly controlling the behavior of `when` and `map` methods.
+
+ # This section controls Freezed code generation.
+ freezed:
+ options:
+ map: false
+ when:
+ when: false
+ maybe_when: false
+ when_or_null: true
+ generate_for:
+ include:
+ - lib/commons/cubits/*.cubit.dart
+ - lib/commons/models/*.model.dart
+
+ # Json Serializable Generators
+ #
+ # Json Serializable is used to generate serialization and deserialization logic for models.
+ # It can automatically create methods to convert objects to and from JSON format.
+
+ # This section controls JSON serialization and deserialization code generation.
+ json_serializable:
+ options:
+ create_factory: true
+ create_to_json: true
+ explicit_to_json: true
+ field_rename: none
+ include_if_null: true
+ generate_for:
+ include:
+ - lib/commons/models/*.model.dart
+
+ # Injectable Generators
+ #
+ # Injectable is used to generate dependency injection code for the app. It automatically generates
+ # registration code for classes annotated with `@Injectable` or `@LazySingleton`.
+
+ # This section controls the Injectable dependency injection code generation.
+ injectable_generator:injectable_builder:
+ generate_for:
+ include:
+ - lib/_di/*.dart
+ - lib/commons/cubits/*.cubit.dart
+
+ # Slang Generators
+ #
+ # Slang is used for internationalization (i18n) by generating translation classes. This configuration
+ # controls how translations are handled and generated.
+
+ # This section configures the Slang package for generating i18n (internationalization) translation classes.
+ slang_build_runner:
+ options:
+ locale_handling: false
+ translation_class_visibility: public
+ fallback_strategy: base_locale
+ input_directory: lib/_core/_i18n
+ output_directory: lib/_core/_i18n
+ output_file_name: translations.g.dart
+ key_case: camel
+ key_map_case: camel
+ param_case: camel
+ flat_map: false
\ No newline at end of file
diff --git a/features/_core/lib/_di/_di.dart b/features/_core/lib/_di/_di.dart
new file mode 100644
index 0000000..44adc30
--- /dev/null
+++ b/features/_core/lib/_di/_di.dart
@@ -0,0 +1,39 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-static-class, avoid-ignoring-return-values, prefer-correct-identifier-length
+
+import 'package:deps/packages/get_it.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:feature_auth/_core/_di/_di.dart';
+import 'package:feature_core/_di/_di.config.dart';
+import 'package:feature_core/_router/features.router.dart';
+import 'package:feature_user/_core/_di/_di.dart';
+
+part '_modules.dart';
+
+/// [injectAllFeatures] is responsible for initializing all the feature-specific dependency injection setups.
+///
+/// It configures the DI container [GetIt] with dependencies for various features by calling their respective
+/// injection methods. It also runs the default injection setup for the core app using `@InjectableInit`.
+///
+/// Parameters:
+/// - [di]: The [GetIt] instance representing the DI container.
+/// - [env]: The environment string used to determine which dependencies should be injected (e.g., `dev`, `prod`).
+///
+/// Example usage:
+/// ```dart
+/// injectAllFeatures(di: GetIt.instance, env: 'prod');
+/// ```
+@InjectableInit()
+void injectAllFeatures({required GetIt di, required String env}) {
+ // Initialize the core dependencies using @InjectableInit
+ di.init(environment: env);
+
+ // Initialize feature-specific dependencies
+ injectAuthFeature(di, env);
+ injectUserFeature(di, env);
+}
diff --git a/features/_core/lib/_di/_modules.dart b/features/_core/lib/_di/_modules.dart
new file mode 100644
index 0000000..d7f203f
--- /dev/null
+++ b/features/_core/lib/_di/_modules.dart
@@ -0,0 +1,31 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-match-file-name
+
+part of '_di.dart';
+
+/// [FeaturesRouterModule] provides a lazily initialized instance of [FeaturesRouter].
+///
+/// This module is part of the dependency injection setup and defines how the [FeaturesRouter]
+/// should be provided across the application. The [@module] annotation marks it as an Injectable module
+/// that groups dependency providers. The [@lazySingleton] annotation ensures that a single instance
+/// of [FeaturesRouter] is created lazily (when it's first required).
+///
+/// Usage:
+/// - This class is automatically integrated into the DI system, making the [FeaturesRouter] accessible
+/// wherever it's injected using GetIt or other DI methods.
+///
+/// Example:
+/// ```dart
+/// final router = GetIt.instance();
+/// ```
+@module
+abstract class FeaturesRouterModule {
+ /// Provides a lazily created singleton instance of [FeaturesRouter].
+ @lazySingleton
+ FeaturesRouter get router => FeaturesRouter();
+}
diff --git a/features/_core/lib/_router/enums/navigation_tabs.enum.dart b/features/_core/lib/_router/enums/navigation_tabs.enum.dart
new file mode 100644
index 0000000..8a91fa7
--- /dev/null
+++ b/features/_core/lib/_router/enums/navigation_tabs.enum.dart
@@ -0,0 +1,51 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/auto_route.dart';
+import 'package:feature_core/_router/features.router.gr.dart';
+import 'package:flutter/material.dart';
+
+/// Enum [NavigationTabsEnum] represents different navigation tabs in the application.
+///
+/// Each tab is associated with a screen route, an icon, and a label.
+/// The routes correspond to specific pages in the app, and icons are used for visual representation
+/// in the navigation bar. The labels are currently static and should be localized using translations.
+///
+/// Example usage:
+/// ```dart
+/// NavigationTabsEnum.a.screen;
+/// NavigationTabsEnum.b.icon;
+/// ```
+///
+/// Properties:
+/// - `screen`: The route information for the tab, representing the destination page.
+/// - `icon`: The icon used in the navigation bar for the tab.
+/// - `label`: The label for the tab, which should be moved to the translation system.
+enum NavigationTabsEnum {
+ /// Example tab representing the "Home" page.
+ /// This should be replaced with the actual home page route.
+ a(screen: ARoute(), icon: Icons.home, label: 'Home'),
+
+ /// Example tab representing the "Settings" page.
+ /// This should be replaced with the actual settings page route.
+ b(screen: BRoute(), icon: Icons.settings, label: 'Settings');
+
+ /// The route representing the screen to navigate to.
+ final PageRouteInfo screen;
+
+ /// The icon displayed in the bottom navigation bar.
+ final IconData icon;
+
+ /// The label for the tab, which should be localized.
+ final String label;
+
+ /// Constructor for the [NavigationTabsEnum].
+ const NavigationTabsEnum({
+ required this.screen,
+ required this.icon,
+ required this.label,
+ });
+}
diff --git a/features/_core/lib/_router/features.router.dart b/features/_core/lib/_router/features.router.dart
new file mode 100644
index 0000000..6202974
--- /dev/null
+++ b/features/_core/lib/_router/features.router.dart
@@ -0,0 +1,72 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/features/features.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:feature_core/_router/guards/auth.guard.dart';
+import 'package:feature_core/_router/routes/custom_page_route.dart';
+import 'package:flutter/material.dart';
+
+/// A router class that defines the app's navigation configuration using AutoRoute.
+///
+/// The [FeaturesRouter] handles the routing between the different features of
+/// the app, such as authentication and core infrastructure routes. It includes
+/// custom page transitions and guards for route protection.
+@AutoRouterConfig()
+class FeaturesRouter extends RootStackRouter {
+ /// Router for authentication-related routes.
+ final AuthFeatureRouter authFeatureRouter = AuthFeatureRouter();
+
+ /// Router for infrastructure-related routes.
+ final InfrastructureRouter infrastructureRouter = InfrastructureRouter();
+
+ /// Default route type with a custom transition animation.
+ ///
+ /// This method defines a custom page transition using [CustomPageRoute], which
+ /// adds a transition duration of 500 milliseconds for forward and reverse animations.
+ @override
+ RouteType get defaultRouteType => RouteType.custom(
+ customRouteBuilder: (_, Widget child, AutoRoutePage page) {
+ return CustomPageRoute(
+ builder: (_) {
+ return child;
+ },
+ settings: page,
+ );
+ },
+ durationInMilliseconds: 500,
+ reverseDurationInMilliseconds: 500,
+ );
+
+ /// List of routes in the app.
+ ///
+ /// This method defines the application's routing structure, including the main
+ /// wrapper (`SuperWrapper`), the `DashboardRouter` which contains child routes
+ /// and route guards, as well as additional routes for authentication and infrastructure.
+ @override
+ List get routes => [
+ AutoRoute(
+ page: SuperWrapper.page,
+ children: [
+ AutoRoute(
+ page: DashboardRouter.page,
+ guards: const [AuthGuard()],
+ children: [
+ // TODO: Routes in the dashboard should be placed here.
+ AutoRoute(page: ARoute.page),
+ AutoRoute(page: BRoute.page),
+ ],
+ initial: true,
+ ),
+ ...authFeatureRouter.routes,
+ // TODO: Other routes should be placed here.
+ ...infrastructureRouter.routes,
+ ],
+ initial: true,
+ ),
+ ];
+}
diff --git a/features/_core/lib/_router/guards/auth.guard.dart b/features/_core/lib/_router/guards/auth.guard.dart
new file mode 100644
index 0000000..13ea5c9
--- /dev/null
+++ b/features/_core/lib/_router/guards/auth.guard.dart
@@ -0,0 +1,62 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: avoid-dynamic, avoid-ignoring-return-values, avoid-unassigned-stream-subscriptions
+
+import 'dart:async';
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/locator/locator.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:feature_auth/_core/_router/router.gr.dart';
+import 'package:feature_auth/views/cubits/auth.cubit.dart';
+import 'package:feature_user/views/cubits/user.cubit.dart';
+
+/// [AuthGuard] is a custom route guard used to protect routes that require user authentication.
+///
+/// It implements the [AutoRouteGuard] interface and is responsible for handling route navigation based on the user's
+/// authentication status. If the user is authenticated and the user data is available, the navigation proceeds;
+/// otherwise, the user is redirected to the authentication route.
+///
+/// Example usage:
+/// ```dart
+/// AutoRoute(
+/// path: '/protected',
+/// page: ProtectedPage,
+/// guards: [AuthGuard()],
+/// )
+/// ```
+class AuthGuard extends AutoRouteGuard {
+ /// Creates an instance of [AuthGuard] with the default constructor.
+ const AuthGuard();
+
+ /// Handles the navigation when a user attempts to access a guarded route.
+ ///
+ /// If the user is authenticated (checked via the [AuthCubit]) and their user data is available
+ /// (checked via the [UserCubit]), the route navigation proceeds by calling [resolver.next].
+ ///
+ /// If the user is unauthenticated, they are redirected to the authentication route using
+ /// [resolver.redirect], replacing the current route.
+ ///
+ /// - [resolver]: The resolver allows you to either continue navigation or redirect the user.
+ /// - [router]: The current router stack that manages the navigation flow.
+ @override
+ Future onNavigation(
+ NavigationResolver resolver, StackRouter router) async {
+ // Check if the user is authenticated and user data exists.
+ Future.delayed(Duration.zero, () async {
+ if (locator().state == AuthStatusEnum.authenticated &&
+ locator().state.user.isNotEmpty) {
+ // Proceed with the navigation if authenticated.
+ resolver.next();
+ }
+ // Redirect to the authentication route if unauthenticated.
+ else if (locator().state == AuthStatusEnum.unauthenticated) {
+ await resolver.redirect(const AuthRoute(), replace: true);
+ }
+ });
+ }
+}
diff --git a/features/_core/lib/_router/routes/custom_page_route.dart b/features/_core/lib/_router/routes/custom_page_route.dart
new file mode 100644
index 0000000..aad3a61
--- /dev/null
+++ b/features/_core/lib/_router/routes/custom_page_route.dart
@@ -0,0 +1,41 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+
+/// A custom page route that extends [MaterialPageRoute] to provide custom transition duration.
+///
+/// This class allows you to define a page route with a custom transition animation duration
+/// while maintaining the default behavior of [MaterialPageRoute].
+///
+/// Example usage:
+/// ```dart
+/// Navigator.push(
+/// context,
+/// CustomPageRoute(
+/// builder: (context) => YourScreen(),
+/// settings: RouteSettings(name: 'your_screen'),
+/// ),
+/// );
+/// ```
+///
+/// [T] is the type of the return value of the route.
+///
+/// - `transitionDuration`: Overridden to define the custom transition duration of 500 milliseconds.
+class CustomPageRoute extends MaterialPageRoute {
+ /// Creates a custom page route with the specified builder and route settings.
+ ///
+ /// - [builder]: A function that returns the widget to be displayed by this route.
+ /// - [settings]: Route-specific settings, such as the name of the route.
+ CustomPageRoute({required super.builder, required super.settings});
+
+ /// Custom transition duration for the route.
+ ///
+ /// This overrides the default transition duration of [MaterialPageRoute]
+ /// and provides a 500 millisecond transition.
+ @override
+ Duration get transitionDuration => const Duration(milliseconds: 500);
+}
diff --git a/features/_core/lib/commons/pages/a.page.dart b/features/_core/lib/commons/pages/a.page.dart
new file mode 100644
index 0000000..fcd892b
--- /dev/null
+++ b/features/_core/lib/commons/pages/a.page.dart
@@ -0,0 +1,38 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:flutter/cupertino.dart';
+
+/// A simple page represented by [APage] which serves as a placeholder.
+///
+/// This page is built using [CupertinoPageScaffold] and showcases the use of a Cupertino style
+/// navigation bar. The page displays a `Placeholder` widget and a text label in the navigation bar.
+///
+// TODO: These page should be deleted and created as feature.
+@RoutePage()
+class APage extends StatelessWidget {
+ const APage({super.key});
+
+ /// Builds the UI for the page.
+ ///
+ /// The method returns a [CupertinoPageScaffold] with a navigation bar and a placeholder
+ /// widget as the page's content.
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ navigationBar: CupertinoNavigationBar(
+ // Sets the title of the page using a custom text style from the theme.
+ middle: 'Home'.text(style: $.theme.fonts.h1),
+ // Disables transition between routes for the navigation bar.
+ transitionBetweenRoutes: false,
+ ),
+ // Displays a simple placeholder as the body of the page.
+ child: const Placeholder(),
+ );
+ }
+}
diff --git a/features/_core/lib/commons/pages/b.page.dart b/features/_core/lib/commons/pages/b.page.dart
new file mode 100644
index 0000000..7417f05
--- /dev/null
+++ b/features/_core/lib/commons/pages/b.page.dart
@@ -0,0 +1,50 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/design/design.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:feature_auth/views/cubits/auth.cubit.dart';
+import 'package:flutter/cupertino.dart';
+
+/// A basic settings page represented by [BPage].
+///
+/// This page contains a Cupertino-style UI with a logout button that triggers
+/// the logout functionality of the application. It is currently used as a placeholder
+/// and should be refactored as part of a feature module.
+///
+// TODO: These page should be deleted and created as feature.
+@RoutePage()
+class BPage extends StatelessWidget {
+ const BPage({super.key});
+
+ /// Builds the UI for the settings page.
+ ///
+ /// The method returns a [CupertinoPageScaffold] with a navigation bar and a centered
+ /// logout button that logs the user out when pressed.
+ @override
+ Widget build(BuildContext context) {
+ return CupertinoPageScaffold(
+ navigationBar: CupertinoNavigationBar(
+ // Sets the title of the page using a custom text style from the theme.
+ middle: 'Settings'.text(style: $.theme.fonts.h1),
+ // Disables transition between routes for the navigation bar.
+ transitionBetweenRoutes: false,
+ ),
+ // Displays a logout button in the center of the page.
+ child: Center(
+ child: CupertinoButton(
+ // Styles the button's background and text color using the theme.
+ color: $.theme.colors.blue,
+ // The button's label is styled with the theme's h2 font and white text color.
+ child: 'Logout'.styled(style: $.theme.fonts.h2.white),
+ // Logs out the user when the button is pressed.
+ onPressed: () => $.get().logout(useBackend: false),
+ ),
+ ),
+ );
+ }
+}
diff --git a/features/_core/lib/commons/routers/dashboard.router.dart b/features/_core/lib/commons/routers/dashboard.router.dart
new file mode 100644
index 0000000..4c38c17
--- /dev/null
+++ b/features/_core/lib/commons/routers/dashboard.router.dart
@@ -0,0 +1,83 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: always_specify_types
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:feature_user/views/cubits/user.cubit.dart';
+import 'package:flutter/cupertino.dart';
+
+/// [DashboardRouter] is the main entry point for a tabbed navigation UI using `AutoRoute`.
+///
+/// This widget builds an [AutoTabsScaffold] that contains the navigation structure
+/// for the authenticated user. The tabs are dynamically generated based on the user's
+/// authentication status and are retrieved from the `UserCubit`.
+///
+/// The [CupertinoTabBar] is used as the bottom navigation bar to switch between
+/// different tabs. The tab structure can be adjusted based on the routes returned
+/// by the [UserCubit].
+@RoutePage()
+class DashboardRouter extends StatefulWidget {
+ const DashboardRouter({super.key});
+
+ @override
+ State createState() => _DashboardRouterState();
+}
+
+/// The state for the [DashboardRouter] widget.
+///
+/// This class manages the state of the tabbed navigation structure, including
+/// the setup of a [HeroController] for smooth page transitions.
+class _DashboardRouterState extends State {
+ final HeroController _heroController = HeroController();
+
+ /// Builds the main UI structure using [AutoTabsScaffold].
+ ///
+ /// The tabs are populated dynamically based on the authenticated user's
+ /// available navigation tabs, which are fetched from the `UserCubit`.
+ @override
+ Widget build(BuildContext context) {
+ return AutoTabsScaffold(
+ // Retrieves the list of navigation tabs available for the authenticated user.
+ routes: $
+ .get()
+ .getAuthenticatedNavigationTabs
+ .map((tab) => tab.screen)
+ .toList(),
+ // Defines the bottom navigation bar using a CupertinoTabBar.
+ bottomNavigationBuilder: (_, TabsRouter tabsRouter) {
+ return BlocBuilder(
+ builder: (_, __) {
+ return CupertinoTabBar(
+ currentIndex: tabsRouter.activeIndex,
+ // Changes the active tab when a user taps on a navigation item.
+ onTap: tabsRouter.setActiveIndex,
+ // Maps the available tabs into navigation items.
+ items:
+ $.get().getAuthenticatedNavigationTabs.map((tab) {
+ return BottomNavigationBarItem(
+ icon: Icon(tab.icon),
+ label: tab.label,
+ );
+ }).toList(),
+ );
+ },
+ );
+ },
+ // Sets up navigation observers, including the [HeroController].
+ navigatorObservers: () => [_heroController],
+ );
+ }
+
+ /// Disposes of resources such as the [HeroController] when the widget is removed from the tree.
+ @override
+ void dispose() {
+ _heroController.dispose();
+ super.dispose();
+ }
+}
diff --git a/features/_core/lib/core.dart b/features/_core/lib/core.dart
new file mode 100644
index 0000000..2a71f29
--- /dev/null
+++ b/features/_core/lib/core.dart
@@ -0,0 +1,7 @@
+export 'package:feature_auth/auth.dart';
+export 'package:feature_user/user.dart';
+
+export '_di/_di.dart';
+export '_router/enums/navigation_tabs.enum.dart';
+export '_router/features.router.dart';
+export '_router/features.router.gr.dart';
diff --git a/features/_core/pubspec.yaml b/features/_core/pubspec.yaml
new file mode 100644
index 0000000..7a9422e
--- /dev/null
+++ b/features/_core/pubspec.yaml
@@ -0,0 +1,27 @@
+name: feature_core
+description: Core for all features of the flutter advanced boilerplate.
+publish_to: none
+version: 0.0.1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ deps:
+ path: ../../deps
+ feature_auth:
+ path: ../auth
+ feature_user:
+ path: ../user
+ flutter:
+ sdk: flutter
+ json_annotation: ^4.9.0
+
+dev_dependencies:
+ auto_route_generator: ^9.0.0
+ build_runner: ^2.4.13
+ dart_code_metrics_presets: ^2.16.0
+ freezed: ^2.5.7
+ injectable_generator: ^2.6.2
+ json_serializable: ^6.8.0
+ slang_build_runner: ^3.31.0
diff --git a/features/_core/pubspec_overrides.yaml b/features/_core/pubspec_overrides.yaml
new file mode 100644
index 0000000..1f34b60
--- /dev/null
+++ b/features/_core/pubspec_overrides.yaml
@@ -0,0 +1,12 @@
+# melos_managed_dependency_overrides: deps,design,feature_auth,feature_user,infrastructure
+dependency_overrides:
+ deps:
+ path: ../../deps
+ design:
+ path: ../../design
+ feature_auth:
+ path: ../auth
+ feature_user:
+ path: ../user
+ infrastructure:
+ path: ../../infrastructure
diff --git a/features/auth/build.yaml b/features/auth/build.yaml
new file mode 100644
index 0000000..2f1daf3
--- /dev/null
+++ b/features/auth/build.yaml
@@ -0,0 +1,113 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+
+targets:
+ $default:
+ builders:
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_router_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/_core/_router/router.dart
+
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_route_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/views/*.page.dart
+ - lib/views/**/*.page.dart
+
+ # Freezed Generators
+ #
+ # Freezed is used to generate immutable data classes, unions, and sealed classes. This section configures how
+ # Freezed generates code, particularly controlling the behavior of `when` and `map` methods.
+
+ # This section controls Freezed code generation.
+ freezed:
+ options:
+ map: false
+ when:
+ when: false
+ maybe_when: false
+ when_or_null: true
+ generate_for:
+ include:
+ - lib/_core/_di/*.dart
+ - lib/models/*.model.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Json Serializable Generators
+ #
+ # Json Serializable is used to generate serialization and deserialization logic for models.
+ # It can automatically create methods to convert objects to and from JSON format.
+
+ # This section controls JSON serialization and deserialization code generation.
+ json_serializable:
+ options:
+ create_factory: true
+ create_to_json: true
+ explicit_to_json: true
+ field_rename: none
+ include_if_null: true
+ generate_for:
+ include:
+ - lib/models/*.model.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Injectable Generators
+ #
+ # Injectable is used to generate dependency injection code for the app. It automatically generates
+ # registration code for classes annotated with `@Injectable` or `@LazySingleton`.
+
+ # This section controls the Injectable dependency injection code generation.
+ injectable_generator:injectable_builder:
+ generate_for:
+ include:
+ - lib/_core/_di/*.dart
+ - lib/services/*.service.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Slang Generators
+ #
+ # Slang is used for internationalization (i18n) by generating translation classes. This configuration
+ # controls how translations are handled and generated.
+
+ # This section configures the Slang package for generating i18n (internationalization) translation classes.
+ slang_build_runner:
+ options:
+ locale_handling: false
+ translation_class_visibility: public
+ fallback_strategy: base_locale
+ input_directory: lib/_core/_i18n
+ output_directory: lib/_core/_i18n
+ output_file_name: translations.g.dart
+ key_case: camel
+ key_map_case: camel
+ param_case: camel
+ flat_map: false
\ No newline at end of file
diff --git a/features/auth/lib/_core/_di/_di.dart b/features/auth/lib/_core/_di/_di.dart
new file mode 100644
index 0000000..21eed32
--- /dev/null
+++ b/features/auth/lib/_core/_di/_di.dart
@@ -0,0 +1,29 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/get_it.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:feature_auth/_core/_di/_di.config.dart';
+
+/// Initializes the dependency injection (DI) system for the auth feature.
+///
+/// This function is responsible for injecting dependencies needed for the auth,
+/// using the `GetIt` package for service location and the `Injectable` package for
+/// automatic dependency injection setup.
+///
+/// The DI setup is controlled by the [env] parameter, allowing different configurations
+/// for different environments (e.g., development, production).
+///
+/// The injected dependencies are defined in the `_di.config.dart` file, which is generated
+/// by the `Injectable` package based on annotations in the project.
+///
+/// - [di]: An instance of `GetIt` used for dependency injection.
+/// - [env]: A string specifying the environment (e.g., 'dev', 'prod') to determine the setup.
+@InjectableInit()
+void injectAuthFeature(GetIt di, String env) {
+ // Initialize the dependencies for the specified environment.
+ di.init(environment: env);
+}
diff --git a/features/auth/lib/_core/_i18n/auth_i18n_cubit_locale.ext.dart b/features/auth/lib/_core/_i18n/auth_i18n_cubit_locale.ext.dart
new file mode 100644
index 0000000..afcf767
--- /dev/null
+++ b/features/auth/lib/_core/_i18n/auth_i18n_cubit_locale.ext.dart
@@ -0,0 +1,28 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:feature_auth/_core/_i18n/translations.g.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `Cubit` to easily access the auth's i18n (internationalization) functionality.
+///
+/// This extension provides a convenient way to retrieve the appropriate `Translations` object for the current locale
+/// managed by a `Cubit`. It uses `AppLocaleUtils.parse` to map the `Locale` to a specific `AppLocale` and
+/// then builds the `Translations` object, which contains localized strings for the app.
+///
+/// Example usage:
+/// ```dart
+/// final translations = $.tr.auth;
+/// ```
+extension AuthI18nCubitLocaleExt on Cubit {
+ /// Retrieves the `Translations` object for the current locale.
+ ///
+ /// This method maps the `Locale` stored in the cubit's state to an `AppLocale` using `AppLocaleUtils.parse`.
+ /// It then builds and returns the corresponding `Translations` object, which provides localized strings
+ /// for use in the application.
+ Translations get auth => AppLocaleUtils.parse(state.toString()).build();
+}
diff --git a/features/auth/lib/_core/_i18n/strings_en.i18n.json b/features/auth/lib/_core/_i18n/strings_en.i18n.json
new file mode 100644
index 0000000..187dd2d
--- /dev/null
+++ b/features/auth/lib/_core/_i18n/strings_en.i18n.json
@@ -0,0 +1,7 @@
+{
+ "title": "Authentication",
+ "header": "Login to your account",
+ "button": {
+ "login": "Log in"
+ }
+}
\ No newline at end of file
diff --git a/features/auth/lib/_core/_i18n/strings_tr.i18n.json b/features/auth/lib/_core/_i18n/strings_tr.i18n.json
new file mode 100644
index 0000000..dc8c24b
--- /dev/null
+++ b/features/auth/lib/_core/_i18n/strings_tr.i18n.json
@@ -0,0 +1,7 @@
+{
+ "title": "Yetkilendirme",
+ "header": "Hesabınıza giriş yapın",
+ "button": {
+ "login": "Giriş Yap"
+ }
+}
\ No newline at end of file
diff --git a/features/auth/lib/_core/_router/router.dart b/features/auth/lib/_core/_router/router.dart
new file mode 100644
index 0000000..1e46672
--- /dev/null
+++ b/features/auth/lib/_core/_router/router.dart
@@ -0,0 +1,30 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-match-file-name
+
+import 'package:deps/packages/auto_route.dart';
+import 'package:feature_auth/_core/_router/router.gr.dart';
+
+/// [AuthFeatureRouter] manages the routing for the authentication feature.
+///
+/// This router is responsible for handling all the routes associated with the
+/// authentication flow of the app. It defines a route for the authentication page
+/// and integrates with AutoRoute for automatic routing.
+@AutoRouterConfig()
+class AuthFeatureRouter extends RootStackRouter {
+ /// Defines the list of routes for the authentication feature.
+ ///
+ /// The primary route for the authentication flow is the [AuthRoute], which
+ /// is accessible via the path `'auth'`.
+ @override
+ List get routes => [
+ AutoRoute(
+ page: AuthRoute.page,
+ path: 'auth',
+ ),
+ ];
+}
diff --git a/features/auth/lib/auth.dart b/features/auth/lib/auth.dart
new file mode 100644
index 0000000..6fca138
--- /dev/null
+++ b/features/auth/lib/auth.dart
@@ -0,0 +1,4 @@
+export '_core/_router/router.dart';
+export '_core/_router/router.gr.dart';
+export 'services/auth.service.dart';
+export 'views/cubits/auth.cubit.dart';
diff --git a/features/auth/lib/services/auth.service.dart b/features/auth/lib/services/auth.service.dart
new file mode 100644
index 0000000..0387c96
--- /dev/null
+++ b/features/auth/lib/services/auth.service.dart
@@ -0,0 +1,99 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: always_specify_types
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/fpdart.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:feature_auth/services/failures/auth.failures.dart';
+
+/// The `AuthService` is responsible for handling user authentication
+/// operations, such as logging in and logging out, through the API.
+///
+/// This class uses `IApiClient` to interact with the backend and
+/// returns responses wrapped in `AsyncEither` to ensure proper
+/// error handling.
+///
+/// Example usage:
+/// ```dart
+/// final Either result = await _service.login(username: 'user', password: 'pass');
+/// result.fold(
+/// (failure) => print('Login failed: $failure'),
+/// (token) => print('Login succeeded: $token'),
+/// );
+/// ```
+@lazySingleton
+class AuthService {
+ /// Constructs the `AuthService` with the required API client.
+ AuthService(this._client);
+
+ /// The API client used for making HTTP requests.
+ final IApiClient _client;
+
+ /// Attempts to log in with the provided `username` and `password`.
+ ///
+ /// On success, it returns a `TokenModel` containing the access token,
+ /// refresh token, expiration date, and ID token. If the login fails,
+ /// it returns an `ExampleAuthFailure`.
+ ///
+ /// Example usage:
+ /// ```dart
+ /// final Either result = await _service.login(username: 'user', password: 'pass');
+ /// ```
+ AsyncEither login({
+ required String username,
+ required String password,
+ }) async {
+ try {
+ // Mocked successful login response, returning a TokenModel.
+ return Right(
+ TokenModel(
+ accessToken: 'accessToken',
+ refreshToken: 'refreshToken',
+ expirationDate: DateTime(2100),
+ idToken: 'idToken',
+ ),
+ );
+ } catch (exception) {
+ // If an error occurs during the login process, it wraps the exception
+ // in an `ExampleAuthFailure` and returns it as a `Left` value.
+ final ExampleAuthFailure failure =
+ ExampleAuthFailure(exception: exception);
+ return Left(failure);
+ }
+ }
+
+ /// Attempts to log out using the provided `idToken`.
+ ///
+ /// The logout request is sent via a POST request, and the result is wrapped
+ /// in an `AsyncEither`. If the logout is successful, it returns a
+ /// `Right(null)`, otherwise, it returns a `Failure`.
+ ///
+ /// Example usage:
+ /// ```dart
+ /// final Either result = await _service.logout(idToken);
+ /// result.fold(
+ /// (failure) => print('Logout failed: $failure'),
+ /// (_) => print('Logout succeeded'),
+ /// );
+ /// ```
+ AsyncEither logout(String idToken) async {
+ // Make an API call to the 'logout' endpoint and handle the response.
+ final Either response = await _client.invoke(
+ 'logout',
+ RequestTypeEnum.post,
+ );
+
+ // Return either a success (Right) or a failure (Left).
+ return response.fold(
+ Left.new,
+ (bool isSucceeded) async {
+ return const Right(null);
+ },
+ );
+ }
+}
diff --git a/features/auth/lib/services/failures/auth.failures.dart b/features/auth/lib/services/failures/auth.failures.dart
new file mode 100644
index 0000000..5295057
--- /dev/null
+++ b/features/auth/lib/services/failures/auth.failures.dart
@@ -0,0 +1,41 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-match-file-name
+
+import 'package:deps/infrastructure/infrastructure.dart';
+
+/// [ExampleAuthFailure] represents a failure case that occurs during an example
+/// authentication process. It extends the [Failure] class and categorizes this
+/// failure as an authentication-related error.
+///
+/// This class is useful for identifying specific authentication failures, enabling
+/// better error handling and troubleshooting.
+///
+/// Example usage:
+/// ```dart
+/// try {
+/// // Example authentication logic
+/// } catch (e, stack) {
+/// throw ExampleAuthFailure(exception: e, stack: stack);
+/// }
+/// ```
+class ExampleAuthFailure extends Failure {
+ /// Creates an instance of [ExampleAuthFailure].
+ ///
+ /// - [exception]: The original exception that triggered the failure.
+ /// - [stack]: The stack trace when the failure occurred.
+ ///
+ /// The failure is initialized with a specific message, code, and failure type,
+ /// indicating that it is related to an authentication failure scenario.
+ ExampleAuthFailure({super.exception, super.stack})
+ : super(
+ code: 'example-auth-failure',
+ type: FailureTypeEnum.exception,
+ tag: FailureTagEnum.authentication,
+ message: 'We encountered a failure while authenticating.',
+ );
+}
diff --git a/features/auth/lib/views/auth.page.dart b/features/auth/lib/views/auth.page.dart
new file mode 100644
index 0000000..e8912e6
--- /dev/null
+++ b/features/auth/lib/views/auth.page.dart
@@ -0,0 +1,96 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: avoid-unnecessary-stateful-widgets, prefer-extracting-callbacks, prefer-moving-to-variable
+
+import 'package:deps/design/design.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/auto_route.dart';
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:deps/packages/reactive_forms.dart';
+import 'package:feature_auth/_core/_i18n/auth_i18n_cubit_locale.ext.dart';
+import 'package:feature_auth/views/cubits/auth.cubit.dart';
+import 'package:feature_auth/views/forms/login.form.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+/// The [AuthPage] is responsible for displaying the login UI and handling user authentication.
+/// It uses [ReactiveForms] for handling form controls and validation, and the [AuthCubit] for managing authentication logic.
+@RoutePage()
+class AuthPage extends StatefulWidget {
+ /// Creates a new instance of [AuthPage].
+ const AuthPage({super.key});
+
+ @override
+ State createState() => _AuthPageState();
+}
+
+class _AuthPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider(
+ create: (_) => $.get(),
+ child: ReactiveForm(
+ formGroup: LoginForm.form,
+ child: CupertinoPageScaffold(
+ navigationBar: CupertinoNavigationBar(
+ middle: $.tr.auth.title.text(style: $.theme.fonts.h1),
+ ),
+ child: PaddingAll.sm(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ $.tr.auth.header.styled(style: $.theme.fonts.h2.regular),
+ PaddingGap.xxs(),
+ ReactiveTextField(
+ formControlName: 'username',
+ decoration: InputDecoration(
+ labelText: 'Username',
+ labelStyle: $.theme.fonts.body1,
+ focusedBorder: UnderlineInputBorder(
+ borderSide: BorderSide(color: $.theme.colors.blue),
+ ),
+ ),
+ ),
+ ReactiveTextField(
+ formControlName: 'password',
+ decoration: InputDecoration(
+ labelText: 'Password',
+ labelStyle: $.theme.fonts.body1,
+ focusedBorder: UnderlineInputBorder(
+ borderSide: BorderSide(color: $.theme.colors.blue),
+ ),
+ ),
+ ),
+ PaddingGap.md(),
+ ReactiveFormConsumer(
+ builder: (_, FormGroup form, ___) {
+ final String username = form.control('username').value;
+ final String password = form.control('password').value;
+
+ return Center(
+ child: CupertinoButton(
+ color: $.theme.colors.blue,
+ child: $.tr.auth.button.login
+ .styled(style: $.theme.fonts.h2.white),
+ onPressed: form.valid
+ ? () => $
+ .get()
+ .login(username: username, password: password)
+ .ignore()
+ : null,
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/features/auth/lib/views/cubits/auth.cubit.dart b/features/auth/lib/views/cubits/auth.cubit.dart
new file mode 100644
index 0000000..db44612
--- /dev/null
+++ b/features/auth/lib/views/cubits/auth.cubit.dart
@@ -0,0 +1,119 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:async';
+
+import 'package:deps/design/design.dart';
+import 'package:deps/features/features.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:deps/packages/fpdart.dart';
+import 'package:deps/packages/freezed_annotation.dart';
+import 'package:deps/packages/injectable.dart';
+
+part 'auth.cubit.freezed.dart';
+part 'states/auth.state.dart';
+
+/// `AuthCubit` manages the authentication flow of the app, handling login, logout,
+/// and changes to the user's authentication state. It listens for authentication status
+/// changes and communicates with services like `AuthService` and `IApiClient` to manage tokens.
+@lazySingleton
+class AuthCubit extends Cubit {
+ AuthCubit(this._client, this._service) : super(AuthStatusEnum.initial) {
+ _authStatusListener =
+ _client.tokenStorage.authStatus.listen(_onAuthStatusChanged);
+ }
+
+ /// Client responsible for network and token operations.
+ final IApiClient _client;
+
+ /// Auth service responsible for handling login and logout requests.
+ final AuthService _service;
+
+ /// Subscription to the authentication status stream.
+ late final StreamSubscription _authStatusListener;
+
+ /// Listens for changes in the authentication status and updates the state accordingly.
+ void _onAuthStatusChanged(AuthStatusEnum event) {
+ if (state != event) {
+ switch (event) {
+ case AuthStatusEnum.authenticated:
+ emit(event);
+ $.get().init();
+
+ case AuthStatusEnum.unauthenticated:
+ emit(event);
+ $.get().deinit();
+
+ default:
+ }
+ }
+ }
+
+ /// Handles user login by calling the `AuthService` and managing the token.
+ ///
+ /// Shows a loading overlay during the process and stores the token upon success.
+ /// If the login fails, it shows a failure alert.
+ Future login({
+ required String username,
+ required String password,
+ }) async {
+ emit(AuthStatusEnum.loading);
+ await $.overlay.showLoading();
+
+ final Either response = await _service.login(
+ username: username,
+ password: password,
+ );
+
+ await response.fold(
+ $.toast.showAlert,
+ (TokenModel token) async {
+ await _client.tokenStorage.setToken(token);
+ },
+ );
+ }
+
+ /// Logs the user out, either by making a backend call or just clearing local tokens.
+ ///
+ /// This method shows a loading overlay during the process and restores the previous state if the logout fails.
+ Future logout({bool useBackend = true}) async {
+ if (state != AuthStatusEnum.authenticated) {
+ return;
+ }
+
+ final AuthStatusEnum previousState = state;
+ emit(AuthStatusEnum.loading);
+ await $.overlay.showLoading();
+
+ if (useBackend) {
+ final TokenModel? tokens = await _client.tokenStorage.token;
+ final Either response =
+ await _service.logout(tokens!.idToken);
+
+ response.fold(
+ (Failure failure) {
+ $.toast.showAlert(failure);
+ emit(previousState);
+ },
+ (_) {
+ $.overlay.popOverlay();
+ _client.tokenStorage.clearToken();
+ },
+ );
+ } else {
+ await $.overlay.popOverlay();
+ await _client.tokenStorage.clearToken();
+ }
+ }
+
+ /// Closes the authentication status listener when the cubit is disposed.
+ @override
+ Future close() {
+ _authStatusListener.cancel();
+ return super.close();
+ }
+}
diff --git a/features/auth/lib/views/cubits/states/auth.state.dart b/features/auth/lib/views/cubits/states/auth.state.dart
new file mode 100644
index 0000000..ee5d49a
--- /dev/null
+++ b/features/auth/lib/views/cubits/states/auth.state.dart
@@ -0,0 +1,19 @@
+part of '../auth.cubit.dart';
+
+/// `AuthState` represents the different states of the authentication process.
+/// These states are sealed using the `freezed` package for immutability and pattern matching.
+@freezed
+sealed class AuthState with _$AuthState {
+ /// Represents the state when an authentication operation is in progress.
+ const factory AuthState.loading() = AuthStateLoading;
+
+ /// Represents the state when an authentication operation has failed.
+ /// Contains a [Failure] object to describe the failure reason.
+ const factory AuthState.failed(Failure failure) = AuthStateFailed;
+
+ /// Represents the state when the user is unauthenticated.
+ const factory AuthState.unauthenticated() = AuthStateUnauthenticated;
+
+ /// Represents the state when the user is successfully authenticated.
+ const factory AuthState.authenticated() = AuthStateAuthenticated;
+}
diff --git a/features/auth/lib/views/forms/login.form.dart b/features/auth/lib/views/forms/login.form.dart
new file mode 100644
index 0000000..9e15b67
--- /dev/null
+++ b/features/auth/lib/views/forms/login.form.dart
@@ -0,0 +1,33 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: avoid-dynamic
+
+import 'package:deps/packages/reactive_forms.dart';
+
+/// The `LoginForm` class is responsible for managing the login form
+/// which includes `username` and `password` fields.
+/// This form uses the `Reactive Forms` package for handling state and validation.
+abstract final class LoginForm {
+ /// A reactive form group that contains two fields: `username` and `password`.
+ /// - **`username`**: A `FormControl` that is initialized with a default value of `test`.
+ /// - Validators:
+ /// - `RequiredValidator()`: Ensures the username field is not left empty.
+ ///
+ /// - **`password`**: A `FormControl` that is initialized with a default value of `test`.
+ /// - Validators:
+ /// - `RequiredValidator()`: Ensures the password field is not left empty.
+ static final FormGroup form = FormGroup(>{
+ 'username': FormControl(
+ value: 'test',
+ validators: const >[RequiredValidator()],
+ ),
+ 'password': FormControl(
+ value: 'test',
+ validators: const >[RequiredValidator()],
+ ),
+ });
+}
diff --git a/features/auth/pubspec.yaml b/features/auth/pubspec.yaml
new file mode 100644
index 0000000..0cd10eb
--- /dev/null
+++ b/features/auth/pubspec.yaml
@@ -0,0 +1,25 @@
+name: feature_auth
+description: The auth feature of the flutter advanced boilerplate.
+publish_to: none
+version: 0.0.1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ deps:
+ path: ../../deps
+ flutter:
+ sdk: flutter
+ json_annotation: ^4.9.0
+
+dev_dependencies:
+ auto_route_generator: ^9.0.0
+ build_runner: ^2.4.13
+ dart_code_metrics_presets: ^2.16.0
+ flutter_test:
+ sdk: flutter
+ freezed: ^2.5.7
+ injectable_generator: ^2.6.2
+ json_serializable: ^6.8.0
+ slang_build_runner: ^3.31.0
diff --git a/features/auth/pubspec_overrides.yaml b/features/auth/pubspec_overrides.yaml
new file mode 100644
index 0000000..dea562d
--- /dev/null
+++ b/features/auth/pubspec_overrides.yaml
@@ -0,0 +1,12 @@
+# melos_managed_dependency_overrides: deps,design,feature_core,feature_user,infrastructure
+dependency_overrides:
+ deps:
+ path: ../../deps
+ design:
+ path: ../../design
+ feature_core:
+ path: ../_core
+ feature_user:
+ path: ../user
+ infrastructure:
+ path: ../../infrastructure
diff --git a/features/user/build.yaml b/features/user/build.yaml
new file mode 100644
index 0000000..2f1daf3
--- /dev/null
+++ b/features/user/build.yaml
@@ -0,0 +1,113 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+
+targets:
+ $default:
+ builders:
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_router_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/_core/_router/router.dart
+
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+
+ # This section handles the main AutoRoute generator for router.dart files.
+ auto_route_generator:auto_route_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/views/*.page.dart
+ - lib/views/**/*.page.dart
+
+ # Freezed Generators
+ #
+ # Freezed is used to generate immutable data classes, unions, and sealed classes. This section configures how
+ # Freezed generates code, particularly controlling the behavior of `when` and `map` methods.
+
+ # This section controls Freezed code generation.
+ freezed:
+ options:
+ map: false
+ when:
+ when: false
+ maybe_when: false
+ when_or_null: true
+ generate_for:
+ include:
+ - lib/_core/_di/*.dart
+ - lib/models/*.model.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Json Serializable Generators
+ #
+ # Json Serializable is used to generate serialization and deserialization logic for models.
+ # It can automatically create methods to convert objects to and from JSON format.
+
+ # This section controls JSON serialization and deserialization code generation.
+ json_serializable:
+ options:
+ create_factory: true
+ create_to_json: true
+ explicit_to_json: true
+ field_rename: none
+ include_if_null: true
+ generate_for:
+ include:
+ - lib/models/*.model.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Injectable Generators
+ #
+ # Injectable is used to generate dependency injection code for the app. It automatically generates
+ # registration code for classes annotated with `@Injectable` or `@LazySingleton`.
+
+ # This section controls the Injectable dependency injection code generation.
+ injectable_generator:injectable_builder:
+ generate_for:
+ include:
+ - lib/_core/_di/*.dart
+ - lib/services/*.service.dart
+ - lib/views/cubits/*.cubit.dart
+
+ # Slang Generators
+ #
+ # Slang is used for internationalization (i18n) by generating translation classes. This configuration
+ # controls how translations are handled and generated.
+
+ # This section configures the Slang package for generating i18n (internationalization) translation classes.
+ slang_build_runner:
+ options:
+ locale_handling: false
+ translation_class_visibility: public
+ fallback_strategy: base_locale
+ input_directory: lib/_core/_i18n
+ output_directory: lib/_core/_i18n
+ output_file_name: translations.g.dart
+ key_case: camel
+ key_map_case: camel
+ param_case: camel
+ flat_map: false
\ No newline at end of file
diff --git a/features/user/lib/_core/_di/_di.dart b/features/user/lib/_core/_di/_di.dart
new file mode 100644
index 0000000..60ac9c4
--- /dev/null
+++ b/features/user/lib/_core/_di/_di.dart
@@ -0,0 +1,29 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/get_it.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:feature_user/_core/_di/_di.config.dart';
+
+/// Initializes the dependency injection (DI) system for the user feature.
+///
+/// This function is responsible for injecting dependencies needed for the user,
+/// using the `GetIt` package for service location and the `Injectable` package for
+/// automatic dependency injection setup.
+///
+/// The DI setup is controlled by the [env] parameter, allowing different configurations
+/// for different environments (e.g., development, production).
+///
+/// The injected dependencies are defined in the `_di.config.dart` file, which is generated
+/// by the `Injectable` package based on annotations in the project.
+///
+/// - [di]: An instance of `GetIt` used for dependency injection.
+/// - [env]: A string specifying the environment (e.g., 'dev', 'prod') to determine the setup.
+@InjectableInit()
+void injectUserFeature(GetIt di, String env) {
+ // Initialize the dependencies for the specified environment.
+ di.init(environment: env);
+}
diff --git a/features/user/lib/_core/_i18n/i18n_mixin.dart b/features/user/lib/_core/_i18n/i18n_mixin.dart
new file mode 100644
index 0000000..0e33e5e
--- /dev/null
+++ b/features/user/lib/_core/_i18n/i18n_mixin.dart
@@ -0,0 +1,28 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:feature_user/_core/_i18n/translations.g.dart';
+import 'package:flutter/material.dart';
+
+/// Extension on `Cubit` to easily access the user's i18n (internationalization) functionality.
+///
+/// This extension provides a convenient way to retrieve the appropriate `Translations` object for the current locale
+/// managed by a `Cubit`. It uses `AppLocaleUtils.parse` to map the `Locale` to a specific `AppLocale` and
+/// then builds the `Translations` object, which contains localized strings for the app.
+///
+/// Example usage:
+/// ```dart
+/// final translations = $.tr.user;
+/// ```
+extension UserI18nCubitLocaleExt on Cubit {
+ /// Retrieves the `Translations` object for the current locale.
+ ///
+ /// This method maps the `Locale` stored in the cubit's state to an `AppLocale` using `AppLocaleUtils.parse`.
+ /// It then builds and returns the corresponding `Translations` object, which provides localized strings
+ /// for use in the application.
+ Translations get user => AppLocaleUtils.parse(state.toString()).build();
+}
diff --git a/features/user/lib/_core/_i18n/strings_en.i18n.json b/features/user/lib/_core/_i18n/strings_en.i18n.json
new file mode 100644
index 0000000..a9d370a
--- /dev/null
+++ b/features/user/lib/_core/_i18n/strings_en.i18n.json
@@ -0,0 +1,3 @@
+{
+ "test": "Test"
+}
\ No newline at end of file
diff --git a/features/user/lib/_core/_i18n/strings_tr.i18n.json b/features/user/lib/_core/_i18n/strings_tr.i18n.json
new file mode 100644
index 0000000..a9d370a
--- /dev/null
+++ b/features/user/lib/_core/_i18n/strings_tr.i18n.json
@@ -0,0 +1,3 @@
+{
+ "test": "Test"
+}
\ No newline at end of file
diff --git a/features/user/lib/_core/_router/router.dart b/features/user/lib/_core/_router/router.dart
new file mode 100644
index 0000000..ce48177
--- /dev/null
+++ b/features/user/lib/_core/_router/router.dart
@@ -0,0 +1,34 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-match-file-name
+
+import 'package:deps/packages/auto_route.dart';
+
+/// `UserFeatureRouter` is responsible for defining and configuring
+/// the routes related to the User feature module.
+///
+/// This class extends `RootStackRouter` and utilizes the `@AutoRouterConfig()`
+/// annotation to manage routes automatically, enabling code generation for routing.
+///
+/// - **Example usage**:
+/// Define user-related routes inside this router, such as user profile, settings, etc.
+///
+/// - **Returns**: A list of routes to be used by the `AutoRoute` package.
+///
+/// Example:
+/// ```dart
+/// @override
+/// List get routes => [
+/// AutoRoute(page: UserProfilePage),
+/// AutoRoute(page: UserSettingsPage),
+/// ];
+/// ```
+@AutoRouterConfig()
+class UserFeatureRouter extends RootStackRouter {
+ @override
+ List get routes => [];
+}
diff --git a/features/user/lib/models/user.model.dart b/features/user/lib/models/user.model.dart
new file mode 100644
index 0000000..9117538
--- /dev/null
+++ b/features/user/lib/models/user.model.dart
@@ -0,0 +1,78 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: prefer-single-declaration-per-file
+
+import 'package:deps/packages/freezed_annotation.dart';
+
+part 'user.model.freezed.dart';
+part 'user.model.g.dart';
+
+/// `UserModel` represents the user entity with fields for ID, first name,
+/// optional last name, phone number, email, and username.
+/// It is built using the `freezed` package for immutability and offers
+/// JSON serialization capabilities.
+///
+/// ### Fields:
+/// - `id` (required): The unique identifier for the user.
+/// - `firstName` (required): The user's first name.
+/// - `lastName` (optional): The user's last name.
+/// - `phoneNumber` (optional): The user's phone number.
+/// - `email` (optional): The user's email address.
+/// - `username` (optional): The user's username.
+///
+/// ### Factory Constructors:
+/// - `UserModel.fromJson(Map json)`: Deserializes the user data from JSON.
+/// - `UserModel.empty()`: Creates an empty user model, where only the `id` and `firstName` fields are initialized with default empty values.
+///
+/// ### Additional Features:
+/// - **isNotEmpty**: A utility getter that checks if the model is not empty.
+///
+/// ### Example:
+/// ```dart
+/// final user = UserModel(
+/// id: '123',
+/// firstName: 'John',
+/// lastName: 'Doe',
+/// email: 'john.doe@example.com',
+/// );
+///
+/// final json = user.toJson(); // Serialize to JSON
+/// final newUser = UserModel.fromJson(json); // Deserialize from JSON
+///
+/// if (user.isNotEmpty) {
+/// print('User is valid');
+/// }
+/// ```
+
+@freezed
+class UserModel with _$UserModel {
+ /// The default factory constructor for `UserModel`.
+ /// `id` and `firstName` are required fields, while others are optional.
+ const factory UserModel({
+ required String id,
+ required String firstName,
+ String? lastName,
+ String? phoneNumber,
+ String? email,
+ String? username,
+ }) = _UserModel;
+
+ /// Private constructor used by Freezed to implement immutability.
+ const UserModel._();
+
+ /// Factory method for deserializing `UserModel` from JSON.
+ factory UserModel.fromJson(Map json) =>
+ _$UserModelFromJson(json);
+
+ /// Provides an empty/default instance of `UserModel` with default values.
+ /// This is useful when you need a default or uninitialized user model.
+ factory UserModel.empty() => const UserModel(id: '', firstName: '');
+
+ /// A utility getter that checks if the user model is not empty.
+ /// Compares the instance with the result of `UserModel.empty()`.
+ bool get isNotEmpty => this != UserModel.empty();
+}
diff --git a/features/user/lib/models/user_settings.model.dart b/features/user/lib/models/user_settings.model.dart
new file mode 100644
index 0000000..f721712
--- /dev/null
+++ b/features/user/lib/models/user_settings.model.dart
@@ -0,0 +1,62 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/freezed_annotation.dart';
+
+part 'user_settings.model.freezed.dart';
+part 'user_settings.model.g.dart';
+
+/// The `UserSettingsModel` class is a data model representing user-specific
+/// settings, including language preferences, locale, and timezone. It utilizes
+/// the `freezed` package for immutable data structures and provides built-in
+/// support for JSON serialization.
+///
+/// ### Fields:
+/// - `languageCode` (required): The language code for the user's preferred language, e.g., 'en', 'tr'.
+/// - `locale` (required): The user's locale, e.g., 'en-US', 'tr-TR'.
+/// - `timeZone` (required): The user's time zone in the ISO format, e.g., '+03:00'.
+///
+/// ### Features:
+/// - **Immutability**: The `freezed` package ensures that instances of this class are immutable.
+/// - **JSON Serialization**: Provides factory methods for JSON serialization/deserialization.
+///
+/// ### Example:
+/// ```dart
+/// final settings = UserSettingsModel(
+/// languageCode: 'en',
+/// locale: 'en-US',
+/// timeZone: '-05:00',
+/// );
+///
+/// final json = settings.toJson(); // Serialize to JSON
+/// final newSettings = UserSettingsModel.fromJson(json); // Deserialize from JSON
+/// ```
+///
+/// ### Factory Constructors:
+/// - `UserSettingsModel.fromJson(Map json)`: Deserializes from JSON.
+/// - `UserSettingsModel.empty()`: Provides a default instance with predefined settings.
+@freezed
+class UserSettingsModel with _$UserSettingsModel {
+ /// The default factory constructor for `UserSettingsModel`.
+ /// All fields are required and must be provided.
+ const factory UserSettingsModel({
+ required String languageCode,
+ required String locale,
+ required String timeZone,
+ }) = _UserSettingsModel;
+
+ /// Factory method for deserializing `UserSettingsModel` from JSON.
+ factory UserSettingsModel.fromJson(Map json) =>
+ _$UserSettingsModelFromJson(json);
+
+ /// Provides an empty/default instance of `UserSettingsModel` with predefined values.
+ /// Default language is Turkish ('tr'), locale is 'tr-TR', and time zone is '+03:00'.
+ factory UserSettingsModel.empty() => const UserSettingsModel(
+ languageCode: 'tr',
+ locale: 'tr-TR',
+ timeZone: '+03:00',
+ );
+}
diff --git a/features/user/lib/services/user.service.dart b/features/user/lib/services/user.service.dart
new file mode 100644
index 0000000..086b7bc
--- /dev/null
+++ b/features/user/lib/services/user.service.dart
@@ -0,0 +1,66 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: always_specify_types
+
+import 'package:deps/features/features.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/fpdart.dart';
+import 'package:deps/packages/injectable.dart';
+
+/// A service class for handling user-related operations such as fetching
+/// user details from the backend.
+///
+/// The class uses the `IApiClient` to make API calls and returns the result
+/// wrapped in an `AsyncEither`, where it can either return a successful
+/// `UserModel` or a `Failure`.
+///
+/// ### Example:
+/// ```dart
+/// final response = await _service.getUserDetails();
+///
+/// response.fold(
+/// (failure) => print('Failed to fetch user details: $failure'),
+/// (user) => print('User details: $user'),
+/// );
+/// ```
+
+@lazySingleton
+class UserService {
+ /// Constructor for `UserService` which requires an instance of `IApiClient`.
+ const UserService(this._client);
+
+ /// The API client used for making requests.
+ final IApiClient _client;
+
+ /// Fetches user details from the `/details` endpoint.
+ ///
+ /// The method returns an `AsyncEither` which will either return a `Failure`
+ /// in case of an error, or a `UserModel` if the request is successful.
+ ///
+ /// ### Example usage:
+ /// ```dart
+ /// final Either response = await _service.getUserDetails();
+ /// ```
+ ///
+ /// The API call uses a GET request to the `/details` endpoint, and the
+ /// response is expected to be deserialized into a `UserModel`.
+ AsyncEither getUserDetails() async {
+ // Makes an API call to fetch user details and map the result to a UserModel.
+ final Either response =
+ await _client.invoke(
+ '/details',
+ RequestTypeEnum.get,
+ fromJson: UserModel.fromJson,
+ );
+
+ // Processes the response, either returning a Failure or a successful UserModel.
+ return response.fold(
+ Left.new,
+ (UserModel user) async => Right(user),
+ );
+ }
+}
diff --git a/features/user/lib/user.dart b/features/user/lib/user.dart
new file mode 100644
index 0000000..06931b2
--- /dev/null
+++ b/features/user/lib/user.dart
@@ -0,0 +1,3 @@
+export '_core/_router/router.dart';
+export 'models/user.model.dart';
+export 'views/cubits/user.cubit.dart';
diff --git a/features/user/lib/views/cubits/states/user.state.dart b/features/user/lib/views/cubits/states/user.state.dart
new file mode 100644
index 0000000..b1defc7
--- /dev/null
+++ b/features/user/lib/views/cubits/states/user.state.dart
@@ -0,0 +1,58 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+part of '../user.cubit.dart';
+
+/// Represents the various states of the user's authentication and session status.
+///
+/// - `initial`: The initial state before any user data is loaded.
+/// - `loading`: The state when the user data is being loaded.
+/// - `unauthenticated`: Indicates the user is not authenticated.
+/// - `fetchingUserDetails`: The state where user details are being retrieved.
+/// - `authenticated`: The user is authenticated and ready to use the application.
+enum UserStateStatus {
+ initial,
+ loading,
+ unauthenticated,
+ fetchingUserDetails,
+ // TODO: Other additional steps can be added here.
+ authenticated,
+}
+
+/// Represents the state of the user, containing the status of the authentication,
+/// the user details, any failure that occurred, and whether the process failed.
+///
+/// This class is sealed and generated using `freezed` to provide immutability and pattern matching capabilities.
+@freezed
+sealed class UserState with _$UserState {
+ /// Constructor for the `UserState` which includes:
+ /// - [status]: Represents the current status of the user session.
+ /// - [user]: Holds the details of the user.
+ /// - [isFailed]: A flag indicating whether the process has failed.
+ /// - [failure]: Contains the failure information in case of an error.
+ const factory UserState({
+ required UserStateStatus status,
+ required UserModel user,
+ required bool isFailed,
+ required Failure failure,
+ }) = _UserState;
+
+ /// Deserializes a `UserState` object from a JSON map.
+ factory UserState.fromJson(Map json) =>
+ _$UserStateFromJson(json);
+
+ /// Creates the initial state for the `UserState`, where:
+ /// - [status] is set to `UserStateStatus.initial`.
+ /// - [user] is initialized as an empty user.
+ /// - [isFailed] is set to `false`.
+ /// - [failure] is set to an empty `Failure`.
+ factory UserState.initial() => UserState(
+ status: UserStateStatus.initial,
+ user: UserModel.empty(),
+ isFailed: false,
+ failure: Failure.empty(),
+ );
+}
diff --git a/features/user/lib/views/cubits/user.cubit.dart b/features/user/lib/views/cubits/user.cubit.dart
new file mode 100644
index 0000000..24fde59
--- /dev/null
+++ b/features/user/lib/views/cubits/user.cubit.dart
@@ -0,0 +1,115 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: always_specify_types
+
+import 'package:deps/design/design.dart';
+import 'package:deps/features/features.dart';
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/packages/fpdart.dart';
+import 'package:deps/packages/freezed_annotation.dart';
+import 'package:deps/packages/hydrated_bloc.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:feature_user/services/user.service.dart';
+import 'package:flutter/material.dart';
+
+part 'states/user.state.dart';
+part 'user.cubit.freezed.dart';
+part 'user.cubit.g.dart';
+
+/// The `UserCubit` class is responsible for managing the state related to the user within the application.
+/// It uses `HydratedCubit` to persist user state between app sessions and interacts with the `UserService`
+/// to fetch user data when necessary.
+///
+/// This cubit holds information about the authenticated user and manages their navigation tabs.
+/// It also handles initialization and deinitialization of the user's session.
+@lazySingleton
+class UserCubit extends HydratedCubit {
+ /// Constructor to initialize the `UserCubit` with the required `UserService`.
+ UserCubit(this._service) : super(UserState.initial());
+
+ /// The service used for fetching user-related data.
+ final UserService _service;
+
+ /// Restores the user's state from a JSON object.
+ ///
+ /// If an error occurs during deserialization, the user is logged out and the initial state is restored.
+ @override
+ UserState fromJson(Map json) {
+ try {
+ return UserState.fromJson(json);
+ } catch (e) {
+ $.get().logout(useBackend: false);
+ return UserState.initial();
+ }
+ }
+
+ /// Serializes the current user state to a JSON object, omitting any failures.
+ @override
+ Map toJson(UserState state) {
+ return state.copyWith(failure: Failure.empty()).toJson();
+ }
+
+ /// Retrieves the navigation tabs available to the authenticated user.
+ ///
+ /// This method can be customized to filter out certain tabs based on the user's permissions.
+ List get getAuthenticatedNavigationTabs {
+ // TODO: Users authorized pages can be filtered here.
+ final List tabs = [
+ ...NavigationTabsEnum.values
+ ];
+ return tabs;
+ }
+
+ /// Initializes the user session by showing a loading overlay and fetching user details.
+ ///
+ /// This method is typically called after the user has been authenticated.
+ Future init() async {
+ await $.overlay.showLoading();
+ emit(state.copyWith(status: UserStateStatus.loading));
+
+ // TODO: This section has been added as an example in case there is additional information that needs to be pulled after the user is authorized. Otherwise, if the user information is fetched during the authorization phase, only saving the user to the state is enough and the routing can be completed without the need for an additional step.
+ getUserDetails().ignore();
+ }
+
+ /// Deinitializes the user session and redirects to the authentication page.
+ Future deinit() async {
+ emit(UserState.initial());
+ await $.navigator.replace(const AuthRoute());
+ }
+
+ /// Fetches the authenticated user's details and updates the cubit's state.
+ ///
+ /// If an error occurs, it shows a dialog with the failure message. Upon success, it navigates to the dashboard.
+ Future getUserDetails() async {
+ emit(state.copyWith(status: UserStateStatus.fetchingUserDetails));
+
+ // Mocking the response for demonstration. Replace with actual service call.
+ const Either response = Right(
+ UserModel(
+ id: '0',
+ username: 'test',
+ firstName: 'Fikret',
+ ),
+ ); // await _service.getUserDetails();
+
+ await response.fold(
+ (Failure failure) {
+ // Show dialog with the failure message if fetching user details fails.
+ $.dialog.showDialog(
+ Dialog(
+ insetPadding: $.paddings.xl.all, child: failure.message.text()),
+ );
+ emit(state.copyWith(isFailed: true, failure: failure));
+ },
+ (UserModel user) async {
+ // Upon successful fetching of user details, navigate to the dashboard.
+ emit(state.copyWith(status: UserStateStatus.authenticated, user: user));
+ await $.navigator.replace(const DashboardRouter());
+ },
+ );
+ }
+}
diff --git a/features/user/pubspec.yaml b/features/user/pubspec.yaml
new file mode 100644
index 0000000..262da41
--- /dev/null
+++ b/features/user/pubspec.yaml
@@ -0,0 +1,25 @@
+name: feature_user
+description: The user feature of the flutter advanced boilerplate.
+publish_to: none
+version: 0.0.1
+
+environment:
+ sdk: ">=3.5.3 <4.0.0"
+
+dependencies:
+ deps:
+ path: ../../deps
+ flutter:
+ sdk: flutter
+ json_annotation: ^4.9.0
+
+dev_dependencies:
+ auto_route_generator: ^9.0.0
+ build_runner: ^2.4.13
+ dart_code_metrics_presets: ^2.16.0
+ flutter_test:
+ sdk: flutter
+ freezed: ^2.5.7
+ injectable_generator: ^2.6.2
+ json_serializable: ^6.8.0
+ slang_build_runner: ^3.31.0
diff --git a/features/user/pubspec_overrides.yaml b/features/user/pubspec_overrides.yaml
new file mode 100644
index 0000000..27dc84b
--- /dev/null
+++ b/features/user/pubspec_overrides.yaml
@@ -0,0 +1,12 @@
+# melos_managed_dependency_overrides: deps,design,feature_auth,feature_core,infrastructure
+dependency_overrides:
+ deps:
+ path: ../../deps
+ design:
+ path: ../../design
+ feature_auth:
+ path: ../auth
+ feature_core:
+ path: ../_core
+ infrastructure:
+ path: ../../infrastructure
diff --git a/infrastructure/build.yaml b/infrastructure/build.yaml
new file mode 100644
index 0000000..88f2345
--- /dev/null
+++ b/infrastructure/build.yaml
@@ -0,0 +1,113 @@
+# Copyright 2024 Thomas. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+#
+
+# This `build.yaml` file is used to configure various code generation tools such as Auto Route,
+# Freezed, Json Serializable, Injectable, and Slang for a Flutter project.
+
+targets:
+ $default:
+ builders:
+ # Auto Route Generators
+ #
+ # These settings configure the code generation for the Auto Route package. Auto Route
+ # helps in generating the routing infrastructure for a Flutter app.
+ #
+ # `auto_router_generator`: Generates the primary routing files, caching build options
+ # to improve build performance.
+ #
+ # `auto_route_generator`: Generates route declarations based on the files included
+ # in the specified paths.
+
+ # This section handles the main AutoRoute generator for the router.dart file.
+ auto_route_generator:auto_router_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/_core/_router/router.dart
+
+ # This section handles AutoRoute generation for all .route.dart files and wrapper files.
+ auto_route_generator:auto_route_generator:
+ options:
+ enable_cached_builds: true
+ generate_for:
+ include:
+ - lib/presentation/_core/**/*.route.dart
+ - lib/presentation/wrappers/*.dart
+
+ # Freezed Generators
+ #
+ # Freezed is used to generate immutable data classes, unions, and sealed classes. This section configures how
+ # Freezed generates code, particularly controlling the behavior of `when` and `map` methods.
+
+ # This section controls Freezed code generation.
+ freezed:
+ options:
+ map: false
+ when:
+ when: false
+ maybe_when: false
+ when_or_null: true
+ generate_for:
+ include:
+ - lib/networking/models/*.model.dart
+ - lib/presentation/cubits/*.cubit.dart
+ - lib/presentation/models/*.model.dart
+
+ # Json Serializable Generators
+ #
+ # Json Serializable is used to generate serialization and deserialization logic for models.
+ # It can automatically create methods to convert objects to and from JSON format.
+
+ # This section controls JSON serialization and deserialization code generation.
+ json_serializable:
+ options:
+ create_factory: true
+ create_to_json: true
+ explicit_to_json: true
+ field_rename: none
+ include_if_null: true
+ generate_for:
+ include:
+ - lib/analytics/failure/failure.dart
+ - lib/flavors/*.env.dart
+ - lib/networking/models/*.model.dart
+ - lib/presentation/cubits/*.cubit.dart
+ - lib/presentation/models/*.model.dart
+
+ # Injectable Generators
+ #
+ # Injectable is used to generate dependency injection code for the app. It automatically generates
+ # registration code for classes annotated with `@Injectable` or `@LazySingleton`.
+
+ # This section controls the Injectable dependency injection code generation.
+ injectable_generator:injectable_builder:
+ generate_for:
+ include:
+ - lib/_core/_di/*.dart
+ - lib/analytics/**/*.dart
+ - lib/flavors/*.dart
+ - lib/networking/**/*.dart
+ - lib/storage/**/*.dart
+ - lib/translations/*.dart
+
+ # Slang Generators
+ #
+ # Slang is used for internationalization (i18n) by generating translation classes. This configuration
+ # controls how translations are handled and generated.
+
+ # This section configures the Slang package for generating i18n (internationalization) translation classes.
+ slang_build_runner:
+ options:
+ locale_handling: false
+ translation_class_visibility: public
+ fallback_strategy: base_locale
+ input_directory: lib/_core/_i18n
+ output_directory: lib/_core/_i18n
+ output_file_name: translations.g.dart
+ key_case: camel
+ key_map_case: camel
+ param_case: camel
+ flat_map: false
\ No newline at end of file
diff --git a/infrastructure/devtools_options.yaml b/infrastructure/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /dev/null
+++ b/infrastructure/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/infrastructure/lib/_core/_di/_di.dart b/infrastructure/lib/_core/_di/_di.dart
new file mode 100644
index 0000000..968b742
--- /dev/null
+++ b/infrastructure/lib/_core/_di/_di.dart
@@ -0,0 +1,38 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/dio.dart';
+import 'package:deps/packages/flutter_secure_storage.dart';
+import 'package:deps/packages/get_it.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:deps/packages/internet_connection_checker_plus.dart';
+import 'package:deps/packages/package_info_plus.dart';
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:infrastructure/_core/_di/_di.config.dart';
+import 'package:infrastructure/analytics/reporters/talker/formatter/fancy_talker_log_formatter.dart';
+
+part '_modules.dart';
+
+/// Initializes the dependency injection (DI) system for the infrastructure layer.
+///
+/// This function is responsible for injecting dependencies needed for the infrastructure,
+/// using the `GetIt` package for service location and the `Injectable` package for
+/// automatic dependency injection setup.
+///
+/// The DI setup is controlled by the [env] parameter, allowing different configurations
+/// for different environments (e.g., development, production).
+///
+/// The injected dependencies are defined in the `_di.config.dart` file, which is generated
+/// by the `Injectable` package based on annotations in the project.
+///
+/// - [di]: An instance of `GetIt` used for dependency injection.
+/// - [env]: A string specifying the environment (e.g., 'dev', 'prod') to determine the setup.
+@InjectableInit()
+Future injectInfrastructure(
+ {required GetIt di, required String env}) async {
+ // Initialize the dependencies for the specified environment.
+ await di.init(environment: env);
+}
diff --git a/infrastructure/lib/_core/_di/_modules.dart b/infrastructure/lib/_core/_di/_modules.dart
new file mode 100644
index 0000000..04866d7
--- /dev/null
+++ b/infrastructure/lib/_core/_di/_modules.dart
@@ -0,0 +1,69 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+part of '_di.dart';
+
+/// A module providing the `Talker` instance for logging in the application.
+///
+/// This module uses `TalkerFlutter` for logging with custom settings such as
+/// max line width and a custom formatter (`FancyTalkerLogFormatter`).
+///
+/// The `@module` annotation is used by `Injectable` to automatically
+/// register this dependency in the DI system.
+@module
+abstract class TalkerModule {
+ /// Provides an initialized `Talker` instance for logging purposes.
+ Talker get talker => TalkerFlutter.init(
+ logger: TalkerLogger(
+ settings: TalkerLoggerSettings(maxLineWidth: 100),
+ formatter: FancyTalkerLogFormatter(),
+ ),
+ );
+}
+
+/// A module providing the `Dio` instance for HTTP requests.
+///
+/// This module registers `Dio` as a dependency, which can be injected
+/// wherever HTTP requests are needed within the application.
+@module
+abstract class DioModule {
+ /// Provides an initialized `Dio` instance for making HTTP requests.
+ Dio get dio => Dio();
+}
+
+/// A module providing the `InternetConnection` instance for checking network status.
+///
+/// This module registers `InternetConnection`, used for monitoring network
+/// connectivity within the application.
+@module
+abstract class InternetConnectionModule {
+ /// Provides an initialized `InternetConnection` instance to check internet connectivity.
+ InternetConnection get internetConnection => InternetConnection();
+}
+
+/// A module providing the `FlutterSecureStorage` instance for secure key-value storage.
+///
+/// This module registers `FlutterSecureStorage`, used for securely storing
+/// sensitive information, such as tokens and credentials, with encryption.
+@module
+abstract class FlutterSecureStorageModule {
+ /// Provides an initialized `FlutterSecureStorage` instance with encrypted shared preferences.
+ FlutterSecureStorage get secureStorage => const FlutterSecureStorage(
+ aOptions: AndroidOptions(encryptedSharedPreferences: true),
+ );
+}
+
+/// A module providing application information via `PackageInfo`.
+///
+/// This module registers `PackageInfo`, which can be used to retrieve information
+/// about the app, such as version, build number, etc. The `@preResolve` annotation
+/// ensures that the `PackageInfo` is asynchronously fetched and ready before being injected.
+@module
+abstract class AppInformationModule {
+ /// Asynchronously retrieves app information from the platform.
+ @preResolve
+ Future get appInformation => PackageInfo.fromPlatform();
+}
diff --git a/infrastructure/lib/_core/_i18n/infrastructure_i18n_cubit_locale.ext.dart b/infrastructure/lib/_core/_i18n/infrastructure_i18n_cubit_locale.ext.dart
new file mode 100644
index 0000000..12d7ba9
--- /dev/null
+++ b/infrastructure/lib/_core/_i18n/infrastructure_i18n_cubit_locale.ext.dart
@@ -0,0 +1,29 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:infrastructure/_core/_i18n/translations.g.dart';
+
+/// Extension on `Cubit` to easily access the infrastructure's i18n (internationalization) functionality.
+///
+/// This extension provides a convenient way to retrieve the appropriate `Translations` object for the current locale
+/// managed by a `Cubit`. It uses `AppLocaleUtils.parse` to map the `Locale` to a specific `AppLocale` and
+/// then builds the `Translations` object, which contains localized strings for the app.
+///
+/// Example usage:
+/// ```dart
+/// final translations = $.tr.infrastructure;
+/// ```
+extension InfrastructureI18nCubitLocaleExt on Cubit {
+ /// Retrieves the `Translations` object for the current locale.
+ ///
+ /// This method maps the `Locale` stored in the cubit's state to an `AppLocale` using `AppLocaleUtils.parse`.
+ /// It then builds and returns the corresponding `Translations` object, which provides localized strings
+ /// for use in the application.
+ Translations get infrastructure =>
+ AppLocaleUtils.parse(state.toString()).build();
+}
diff --git a/infrastructure/lib/_core/_i18n/strings_en.i18n.json b/infrastructure/lib/_core/_i18n/strings_en.i18n.json
new file mode 100644
index 0000000..5f235a5
--- /dev/null
+++ b/infrastructure/lib/_core/_i18n/strings_en.i18n.json
@@ -0,0 +1,85 @@
+{
+ "permissions": {
+ "dialog": {
+ "buttons": {
+ "cancel": "Cancel",
+ "ok": "OK",
+ "openSettings": "Open Settings",
+ "retry": "Retry",
+ "understood": "Understood"
+ },
+ "denied": {
+ "description": "@:permissions.type is required to proceed. Please consider enabling it in your settings.",
+ "title": "@:permissions.type Permission Denied"
+ },
+ "limited": {
+ "description": "You have granted limited @:permissions.type access. If you want to allow full access, please adjust in your settings.",
+ "title": "@:permissions.type Permission Limited"
+ },
+ "permanentlyDenied": {
+ "description": "@:permissions.type is required but has been permanently denied. Please enable it from app settings.",
+ "title": "@:permissions.type Permission Permanently Denied"
+ },
+ "provisional": {
+ "description": "The app is provisionally authorized. Some features might be limited.",
+ "title": "Provisional @:permissions.type Permission"
+ },
+ "restricted": {
+ "description": "Access to @:permissions.type is restricted by system policies or parental controls.",
+ "title": "@:permissions.type Permission Restricted"
+ }
+ },
+ "failures": {
+ "invalidPermissionType": "Invalid permission type: $type",
+ "unknownPermissionRequest": "An error occurred while requesting permission."
+ },
+ "type(context=PermissionTypeEnum)": {
+ "accessMediaLocation": "Access Media Location",
+ "accessNotificationPolicy": "Access Notification Policy",
+ "activityRecognition": "Activity Recognition",
+ "appTrackingTransparency": "App Tracking Transparency",
+ "audio": "Audio",
+ "bluetooth": "Bluetooth",
+ "bluetoothAdvertise": "Bluetooth Advertise",
+ "bluetoothConnect": "Bluetooth Connect",
+ "bluetoothScan": "Bluetooth Scan",
+ "calendar": "Calendar",
+ "calendarFullAccess": "Calendar Full Access",
+ "calendarWriteOnly": "Calendar Write Only",
+ "camera": "Camera",
+ "contacts": "Contacts",
+ "criticalAlerts": "Critical Alerts",
+ "ignoreBatteryOptimizations": "Ignore Battery Optimizations",
+ "location": "Location",
+ "locationAlways": "Location Always",
+ "locationWhenInUse": "Location When In Use",
+ "manageExternalStorage": "Manage External Storage",
+ "mediaLibrary": "Media Library",
+ "microphone": "Microphone",
+ "nearbyWifiDevices": "Nearby Wi-Fi Devices",
+ "notification": "Notification",
+ "phone": "Phone",
+ "photos": "Photos",
+ "photosAddOnly": "Photos Add Only",
+ "reminders": "Reminders",
+ "requestInstallPackages": "Request Install Packages",
+ "scheduleExactAlarm": "Schedule Exact Alarm",
+ "sensors": "Sensors",
+ "sensorsAlways": "Sensors Always",
+ "sms": "SMS",
+ "speech": "Speech",
+ "storage": "Storage",
+ "systemAlertWindow": "System Alert Window",
+ "unknown": "Unknown",
+ "videos": "Videos"
+ }
+ },
+ "presentation": {
+ "validations": {
+ "email": "$field should be a valid email address.",
+ "maxLength": "$field cannot be more than $count characters.",
+ "minLength": "$field cannot be less than $count characters.",
+ "required": "$field is required."
+ }
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/lib/_core/_i18n/strings_ro.i18n.json b/infrastructure/lib/_core/_i18n/strings_ro.i18n.json
new file mode 100644
index 0000000..fc63511
--- /dev/null
+++ b/infrastructure/lib/_core/_i18n/strings_ro.i18n.json
@@ -0,0 +1,85 @@
+{
+ "permissions": {
+ "dialog": {
+ "buttons": {
+ "cancel": "Anulează",
+ "ok": "OK",
+ "openSettings": "Deschide Setările",
+ "retry": "Reîncearcă",
+ "understood": "Am înțeles"
+ },
+ "denied": {
+ "description": "@:permissions.type este necesar pentru a continua. Vă rugăm să luați în considerare activarea acestuia în setări.",
+ "title": "Permisiune @:permissions.type Refuzată"
+ },
+ "limited": {
+ "description": "Ați acordat acces limitat la @:permissions.type. Dacă doriți să permiteți accesul complet, vă rugăm să ajustați setările.",
+ "title": "Permisiune @:permissions.type Limitată"
+ },
+ "permanentlyDenied": {
+ "description": "@:permissions.type este necesar, dar a fost refuzat permanent. Vă rugăm să activați permisiunea din setările aplicației.",
+ "title": "Permisiune @:permissions.type Refuzată Permanent"
+ },
+ "provisional": {
+ "description": "Aplicația este autorizată provizoriu. Unele funcții ar putea fi limitate.",
+ "title": "Permisiune @:permissions.type Provizorie"
+ },
+ "restricted": {
+ "description": "Accesul la @:permissions.type este restricționat de politicile sistemului sau de controlul parental.",
+ "title": "Permisiune @:permissions.type Restricționată"
+ }
+ },
+ "failures": {
+ "invalidPermissionType": "Tip de permisiune invalid: $type",
+ "unknownPermissionRequest": "A apărut o eroare în timpul solicitării permisiunii."
+ },
+ "type(context=PermissionTypeEnum)": {
+ "accessMediaLocation": "Accesare Locație Media",
+ "accessNotificationPolicy": "Accesare Politică Notificări",
+ "activityRecognition": "Recunoașterea Activității",
+ "appTrackingTransparency": "Transparența Urmăririi Aplicației",
+ "audio": "Audio",
+ "bluetooth": "Bluetooth",
+ "bluetoothAdvertise": "Publicitate Bluetooth",
+ "bluetoothConnect": "Conectare Bluetooth",
+ "bluetoothScan": "Scanare Bluetooth",
+ "calendar": "Calendar",
+ "calendarFullAccess": "Acces Complet Calendar",
+ "calendarWriteOnly": "Doar Scriere în Calendar",
+ "camera": "Cameră",
+ "contacts": "Contacte",
+ "criticalAlerts": "Alerte Critice",
+ "ignoreBatteryOptimizations": "Ignorare Optimizări Baterie",
+ "location": "Locație",
+ "locationAlways": "Locație Întotdeauna",
+ "locationWhenInUse": "Locație Când Este Folosită",
+ "manageExternalStorage": "Gestionare Stocare Externă",
+ "mediaLibrary": "Bibliotecă Media",
+ "microphone": "Microfon",
+ "nearbyWifiDevices": "Dispozitive Wi-Fi Apropiate",
+ "notification": "Notificare",
+ "phone": "Telefon",
+ "photos": "Fotografii",
+ "photosAddOnly": "Adăugare Doar Fotografii",
+ "reminders": "Memento-uri",
+ "requestInstallPackages": "Solicitare Instalare Pachete",
+ "scheduleExactAlarm": "Programare Alarmă Exactă",
+ "sensors": "Senzori",
+ "sensorsAlways": "Senzori Întotdeauna",
+ "sms": "SMS",
+ "speech": "Vorbire",
+ "storage": "Stocare",
+ "systemAlertWindow": "Fereastră de Avertizare Sistem",
+ "unknown": "Necunoscut",
+ "videos": "Videoclipuri"
+ }
+ },
+ "presentation": {
+ "validations": {
+ "email": "$field trebuie să fie o adresă de email validă.",
+ "maxLength": "$field nu poate avea mai mult de $count caractere.",
+ "minLength": "$field nu poate avea mai puțin de $count caractere.",
+ "required": "$field este obligatoriu."
+ }
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/lib/_core/_i18n/strings_tr.i18n.json b/infrastructure/lib/_core/_i18n/strings_tr.i18n.json
new file mode 100644
index 0000000..a6edd97
--- /dev/null
+++ b/infrastructure/lib/_core/_i18n/strings_tr.i18n.json
@@ -0,0 +1,85 @@
+{
+ "permissions": {
+ "dialog": {
+ "buttons": {
+ "cancel": "İptal",
+ "ok": "Tamam",
+ "openSettings": "Ayarları Aç",
+ "retry": "Yeniden Dene",
+ "understood": "Anladım"
+ },
+ "denied": {
+ "description": "@:permissions.type izni ilerlemek için gereklidir. Lütfen uygulama ayarlarından etkinleştirin.",
+ "title": "@:permissions.type İzni Reddedilmiş"
+ },
+ "limited": {
+ "description": "Sınırlı @:permissions.type erişimi sağladınız. Tam erişim izni vermek istiyorsanız, lütfen ayarlarınızı düzenleyin.",
+ "title": "@:permissions.type İzni Sınırlı"
+ },
+ "permanentlyDenied": {
+ "description": "@:permissions.type izni gerekli ancak kalıcı olarak reddedilmiş. Lütfen uygulama ayarlarından etkinleştirin.",
+ "title": "@:permissions.type İzni Kalıcı Olarak Reddedilmiş"
+ },
+ "provisional": {
+ "description": "Uygulama geçici olarak yetkilendirilmiş. Bazı özellikler sınırlı olabilir.",
+ "title": "Geçici @:permissions.type İzni"
+ },
+ "restricted": {
+ "description": "@:permissions.type erişimi sistem politikaları veya ebeveyn kontrolleri tarafından kısıtlanmıştır.",
+ "title": "@:permissions.type İzni Kısıtlanmış"
+ }
+ },
+ "failures": {
+ "invalidPermissionType": "Geçersiz izin türü: $type",
+ "unknownPermissionRequest": "İzin istenirken bir hata oluştu."
+ },
+ "type(context=PermissionTypeEnum)": {
+ "accessMediaLocation": "Medya Konumuna Erişim",
+ "accessNotificationPolicy": "Bildirim Politikasına Erişim",
+ "activityRecognition": "Aktivite Tanıma",
+ "appTrackingTransparency": "Uygulama Takip Şeffaflığı",
+ "audio": "Ses",
+ "bluetooth": "Bluetooth",
+ "bluetoothAdvertise": "Bluetooth Reklamı",
+ "bluetoothConnect": "Bluetooth Bağlantısı",
+ "bluetoothScan": "Bluetooth Taraması",
+ "calendar": "Takvim",
+ "calendarFullAccess": "Takvime Tam Erişim",
+ "calendarWriteOnly": "Sadece Takvim Yaz",
+ "camera": "Kamera",
+ "contacts": "Kişiler",
+ "criticalAlerts": "Kritik Uyarılar",
+ "ignoreBatteryOptimizations": "Batarya Optimizasyonlarını Yoksay",
+ "location": "Konum",
+ "locationAlways": "Daima Konum",
+ "locationWhenInUse": "Kullanıldığında Konum",
+ "manageExternalStorage": "Harici Depolamayı Yönet",
+ "mediaLibrary": "Medya Kütüphanesi",
+ "microphone": "Mikrofon",
+ "nearbyWifiDevices": "Yakındaki Wi-Fi Cihazları",
+ "notification": "Bildirim",
+ "phone": "Telefon",
+ "photos": "Fotoğraflar",
+ "photosAddOnly": "Sadece Fotoğraf Ekle",
+ "reminders": "Hatırlatıcılar",
+ "requestInstallPackages": "Paket Yükleme İsteği",
+ "scheduleExactAlarm": "Kesin Alarmı Planla",
+ "sensors": "Sensörler",
+ "sensorsAlways": "Daima Sensörler",
+ "sms": "SMS",
+ "speech": "Konuşma",
+ "storage": "Depolama",
+ "systemAlertWindow": "Sistem Uyarı Penceresi",
+ "unknown": "Bilinmeyen",
+ "videos": "Videolar"
+ }
+ },
+ "presentation": {
+ "validations": {
+ "email": "$field geçerli bir eposta adresi olmalıdır.",
+ "maxLength": "$field alanı $count karakterden uzun olamaz.",
+ "minLength": "$field alanı $count karakterden kısa olamaz.",
+ "required": "$field alanı zorunludur."
+ }
+ }
+}
\ No newline at end of file
diff --git a/infrastructure/lib/_core/_router/router.dart b/infrastructure/lib/_core/_router/router.dart
new file mode 100644
index 0000000..5f23a87
--- /dev/null
+++ b/infrastructure/lib/_core/_router/router.dart
@@ -0,0 +1,36 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/auto_route.dart';
+import 'package:infrastructure/_core/_router/router.gr.dart';
+import 'package:infrastructure/presentation/_core/dialog/dialog_builder.dart';
+import 'package:infrastructure/presentation/_core/modal/modal_builder.dart';
+
+/// The main router configuration for the infrastructure layer of the application.
+///
+/// This class extends `RootStackRouter` and defines custom routes for dialogs and modals.
+/// It uses the `AutoRoute` package to manage navigation in a type-safe and declarative way.
+@AutoRouterConfig()
+class InfrastructureRouter extends RootStackRouter {
+ /// Defines the routes for the infrastructure, including custom dialog and modal routes.
+ ///
+ /// - The `DialogWrapperRoute` and `ModalWrapperRoute` are pages that use custom route builders.
+ /// - These routes are built using the custom route builders `DialogBuilder.route` and `ModalBuilder.route`,
+ /// respectively, allowing for a customized presentation of dialogs and modals.
+ @override
+ List get routes => [
+ // Custom route for handling dialog presentations.
+ CustomRoute(
+ page: DialogWrapperRoute.page,
+ customRouteBuilder: DialogBuilder.route,
+ ),
+ // Custom route for handling modal presentations.
+ CustomRoute(
+ page: ModalWrapperRoute.page,
+ customRouteBuilder: ModalBuilder.route,
+ ),
+ ];
+}
diff --git a/infrastructure/lib/_core/commons/converters/bloc_to_listenable.converter.dart b/infrastructure/lib/_core/commons/converters/bloc_to_listenable.converter.dart
new file mode 100644
index 0000000..5bc0b77
--- /dev/null
+++ b/infrastructure/lib/_core/commons/converters/bloc_to_listenable.converter.dart
@@ -0,0 +1,52 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+
+/// A utility class that converts a [Stream] (typically from a BLoC) into a
+/// [ChangeNotifier] so that it can be used with widgets that rely on
+/// [Listenable] interfaces, such as [AnimatedBuilder] or [ValueListenableBuilder].
+///
+/// The class listens to a provided stream and notifies its listeners whenever
+/// a new event is emitted by the stream.
+///
+/// Example usage:
+/// ```dart
+/// final converter = BlocToListenableConverter(myBlocStream);
+/// // Use converter in a widget that listens for changes.
+/// ```
+class BlocToListenableConverter extends ChangeNotifier {
+ /// The stream that this converter listens to.
+ final Stream _stream;
+
+ /// The subscription to the stream. This is used to listen for updates and
+ /// notify the listeners accordingly.
+ late final StreamSubscription _subscription;
+
+ /// Creates an instance of [BlocToListenableConverter] that listens to the
+ /// provided [stream]. Each time a new event is emitted from the stream, it
+ /// calls [notifyListeners] to inform all the listeners.
+ BlocToListenableConverter(this._stream) {
+ _subscription = _stream.listen((T event) {
+ notifyListeners();
+ });
+ }
+
+ /// Cancels the stream subscription and disposes of the resources when
+ /// the [BlocToListenableConverter] is no longer needed.
+ ///
+ /// Always call [dispose] to free up the stream subscription and prevent
+ /// memory leaks.
+ @override
+ void dispose() {
+ _subscription
+ .cancel(); // Cancel the stream subscription to avoid memory leaks.
+ super
+ .dispose(); // Call super to ensure any additional cleanup by ChangeNotifier.
+ }
+}
diff --git a/infrastructure/lib/_core/commons/enums/auth_status.enum.dart b/infrastructure/lib/_core/commons/enums/auth_status.enum.dart
new file mode 100644
index 0000000..de5b4c0
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/auth_status.enum.dart
@@ -0,0 +1,20 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing the different authentication statuses of the application.
+enum AuthStatusEnum {
+ /// User is successfully authenticated.
+ authenticated,
+
+ /// Authentication process is currently loading.
+ loading,
+
+ /// Initial state, before any authentication attempts.
+ initial,
+
+ /// User is not authenticated.
+ unauthenticated,
+}
diff --git a/infrastructure/lib/_core/commons/enums/connectivity_status.enum.dart b/infrastructure/lib/_core/commons/enums/connectivity_status.enum.dart
new file mode 100644
index 0000000..3f03793
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/connectivity_status.enum.dart
@@ -0,0 +1,17 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing the connectivity status of the application.
+enum ConnectivityStatusEnum {
+ /// Device is connected to the network.
+ connected,
+
+ /// Device is not connected to the network.
+ disconnected,
+
+ /// Initial state before any connectivity checks.
+ initial,
+}
diff --git a/infrastructure/lib/_core/commons/enums/env.enum.dart b/infrastructure/lib/_core/commons/enums/env.enum.dart
new file mode 100644
index 0000000..b4cb415
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/env.enum.dart
@@ -0,0 +1,14 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing the different environments of the application.
+enum EnvEnum {
+ /// Development environment.
+ dev,
+
+ /// Production environment.
+ prod,
+}
diff --git a/infrastructure/lib/_core/commons/enums/failure_tag.enum.dart b/infrastructure/lib/_core/commons/enums/failure_tag.enum.dart
new file mode 100644
index 0000000..76f41f1
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/failure_tag.enum.dart
@@ -0,0 +1,35 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing different tags used to categorize failures in the application.
+enum FailureTagEnum {
+ /// Represents an empty or undefined failure.
+ empty,
+
+ /// Failures related to authentication issues.
+ authentication,
+
+ /// Failures caused by network connectivity problems.
+ network,
+
+ /// Failures related to operational issues.
+ operation,
+
+ /// Failures due to insufficient permissions.
+ permission,
+
+ /// Failures in the presentation layer (UI-related).
+ presentation,
+
+ /// Failures related to service call issues.
+ service,
+
+ /// Failures related to application state.
+ state,
+
+ /// Uncaught or uncategorized failures.
+ uncaught,
+}
diff --git a/infrastructure/lib/_core/commons/enums/failure_type.enum.dart b/infrastructure/lib/_core/commons/enums/failure_type.enum.dart
new file mode 100644
index 0000000..d6ebba1
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/failure_type.enum.dart
@@ -0,0 +1,23 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing the different types of failures that can occur in the application.
+enum FailureTypeEnum {
+ /// Used to inform the user that the operation is successful.
+ constructive,
+
+ /// Used to inform the user that the operation is unsuccessful.
+ destructive,
+
+ /// Represents an empty or undefined failure type.
+ empty,
+
+ /// General important errors.
+ error,
+
+ /// Failures caused by exceptions.
+ exception,
+}
diff --git a/infrastructure/lib/_core/commons/enums/log_type.enum.dart b/infrastructure/lib/_core/commons/enums/log_type.enum.dart
new file mode 100644
index 0000000..4b76a5a
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/log_type.enum.dart
@@ -0,0 +1,23 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing different types of log messages.
+enum LogTypeEnum {
+ /// Debugging information.
+ debug,
+
+ /// Error messages.
+ error,
+
+ /// Informational messages.
+ info,
+
+ /// Verbose log messages, used for detailed debugging.
+ verbose,
+
+ /// Warning messages.
+ warning,
+}
diff --git a/infrastructure/lib/_core/commons/enums/request_type.enum.dart b/infrastructure/lib/_core/commons/enums/request_type.enum.dart
new file mode 100644
index 0000000..b37d57c
--- /dev/null
+++ b/infrastructure/lib/_core/commons/enums/request_type.enum.dart
@@ -0,0 +1,23 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Enum representing the different types of API calls.
+enum RequestTypeEnum {
+ /// DELETE request.
+ delete,
+
+ /// GET request.
+ get,
+
+ /// PATCH request.
+ patch,
+
+ /// POST request.
+ post,
+
+ /// PUT request.
+ put,
+}
diff --git a/infrastructure/lib/_core/commons/extensions/color.ext.dart b/infrastructure/lib/_core/commons/extensions/color.ext.dart
new file mode 100644
index 0000000..ba7cbc8
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/color.ext.dart
@@ -0,0 +1,35 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+
+/// Extension on the `Color` class to add custom utility methods.
+extension ColorExt on Color {
+ /// Converts a color to its ARGB representation with an alpha value of 0.
+ Color get toARGB => Color.fromARGB(0, red, green, blue);
+
+ /// Converts a color to its hexadecimal string representation.
+ String get toHex =>
+ "#${value.toRadixString(16).padLeft(8, '0').toUpperCase()}";
+
+ /// Blends the current color with a [blendColor] by a given percentage [percent].
+ ///
+ /// [percent] should be between 0 and 100.
+ Color blend(Color blendColor, int percent) {
+ assert(percent >= 0 && percent <= 100, 'Percent must be between 0 and 100');
+
+ final double mixRatio = percent / 100;
+ final double inverseRatio = 1 - mixRatio;
+
+ // Blend the colors based on the given percentage.
+ return Color.fromARGB(
+ 255,
+ (red * inverseRatio + blendColor.red * mixRatio).toInt(),
+ (green * inverseRatio + blendColor.green * mixRatio).toInt(),
+ (blue * inverseRatio + blendColor.blue * mixRatio).toInt(),
+ );
+ }
+}
diff --git a/infrastructure/lib/_core/commons/extensions/context.ext.dart b/infrastructure/lib/_core/commons/extensions/context.ext.dart
new file mode 100644
index 0000000..f8ce6a0
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/context.ext.dart
@@ -0,0 +1,77 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+import 'package:infrastructure/presentation/super_class.dart';
+
+/// Extension on `BuildContext` to simplify access to various Flutter `ThemeData`, `MediaQuery`, and more.
+extension ContextExt on BuildContext {
+ // Access various themes directly from the context.
+ ThemeData get theme => Theme.of(this);
+ TextTheme get textTheme => Theme.of(this).textTheme;
+ ColorScheme get colorScheme => Theme.of(this).colorScheme;
+ TextTheme get primaryTextTheme => Theme.of(this).primaryTextTheme;
+ BottomAppBarTheme get bottomAppBarTheme => Theme.of(this).bottomAppBarTheme;
+ BottomSheetThemeData get bottomSheetTheme => Theme.of(this).bottomSheetTheme;
+ AppBarTheme get appBarTheme => Theme.of(this).appBarTheme;
+
+ // Access media query properties from the context.
+ Size get mediaQuerySize => MediaQuery.sizeOf(this);
+ double get height => mediaQuerySize.height;
+ double get width => mediaQuerySize.width;
+ EdgeInsets get mediaQueryPadding => MediaQuery.paddingOf(this);
+ EdgeInsets get mediaQueryViewPadding => MediaQuery.viewPaddingOf(this);
+ EdgeInsets get mediaQueryViewInsets => MediaQuery.viewInsetsOf(this);
+ Orientation get orientation => MediaQuery.orientationOf(this);
+
+ // Determine orientation type.
+ bool get isLandscape => orientation == Orientation.landscape;
+ bool get isPortrait => orientation == Orientation.portrait;
+
+ // Check if the 24-hour format should always be used.
+ bool get shouldAlwaysUse24HourFormat =>
+ MediaQuery.alwaysUse24HourFormatOf(this);
+
+ // Access device pixel ratio and platform brightness from the context.
+ double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this);
+ Brightness get platformBrightness => MediaQuery.platformBrightnessOf(this);
+
+ // Determine if the screen size matches phone or tablet dimensions.
+ double get mediaQueryShortestSide => mediaQuerySize.shortestSide;
+ bool get shouldShowNavbar => width > 800;
+ bool get isPhone => mediaQueryShortestSide < 600;
+ bool get isSmallTablet => mediaQueryShortestSide >= 600;
+ bool get isLargeTablet => mediaQueryShortestSide >= 720;
+ bool get isTablet => isSmallTablet || isLargeTablet;
+
+ // Alternative MediaQuery shorthand accessors.
+ Size get mqSize => MediaQuery.sizeOf(this);
+ double get mqHeight => mqSize.height;
+ double get mqWidth => mqSize.width;
+ EdgeInsets get mqPadding => MediaQuery.paddingOf(this);
+ EdgeInsets get mqViewPadding => MediaQuery.viewPaddingOf(this);
+ EdgeInsets get mqViewInsets => MediaQuery.viewInsetsOf(this);
+ Orientation get mqOrientation => MediaQuery.orientationOf(this);
+ bool get mqAlwaysUse24HourFormat => MediaQuery.alwaysUse24HourFormatOf(this);
+ double get mqDevicePixelRatio => MediaQuery.devicePixelRatioOf(this);
+ Brightness get mqPlatformBrightness => MediaQuery.platformBrightnessOf(this);
+
+ /// Returns a value based on the current device type (desktop, mobile, tablet).
+ T? responsiveValue({T? desktop, T? mobile, T? tablet}) {
+ double deviceWidth = mediaQuerySize.shortestSide;
+
+ if ($.platform.isDesktop) {
+ deviceWidth = mediaQuerySize.width;
+ }
+
+ // Check the device width and return the appropriate value for desktop, tablet, or mobile.
+ if (deviceWidth >= 1200 && desktop != null) {
+ return desktop;
+ }
+
+ return deviceWidth >= 600 && tablet != null ? tablet : mobile;
+ }
+}
diff --git a/infrastructure/lib/_core/commons/extensions/date_time.ext.dart b/infrastructure/lib/_core/commons/extensions/date_time.ext.dart
new file mode 100644
index 0000000..f15572c
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/date_time.ext.dart
@@ -0,0 +1,11 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Extension on `DateTime` for potential future utility methods.
+extension DateTimeExt on DateTime {
+ // Currently, there are no additional methods for `DateTime`.
+ // Add custom `DateTime` utility methods here as needed.
+}
diff --git a/infrastructure/lib/_core/commons/extensions/double.ext.dart b/infrastructure/lib/_core/commons/extensions/double.ext.dart
new file mode 100644
index 0000000..8346eec
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/double.ext.dart
@@ -0,0 +1,97 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+
+/// Extension on `double` to simplify the creation of `EdgeInsets` from a single value.
+extension DoubleExt on double {
+ /// Creates an `EdgeInsets` with only the specified sides enabled.
+ ///
+ /// You can specify which sides to apply the padding to by setting the
+ /// boolean flags `b` (bottom), `l` (left), `r` (right), and `t` (top).
+ EdgeInsets only({
+ bool b = false,
+ bool l = false,
+ bool r = false,
+ bool t = false,
+ }) =>
+ EdgeInsets.fromLTRB(
+ l ? this : 0, // Left padding
+ t ? this : 0, // Top padding
+ r ? this : 0, // Right padding
+ b ? this : 0, // Bottom padding
+ );
+
+ /// Creates an `EdgeInsets` with only left padding.
+ EdgeInsets get left => EdgeInsets.only(left: this);
+
+ /// Creates an `EdgeInsets` with only right padding.
+ EdgeInsets get right => EdgeInsets.only(right: this);
+
+ /// Creates an `EdgeInsets` with only top padding.
+ EdgeInsets get top => EdgeInsets.only(top: this);
+
+ /// Creates an `EdgeInsets` with only bottom padding.
+ EdgeInsets get bottom => EdgeInsets.only(bottom: this);
+
+ /// Creates an `EdgeInsets` with equal padding on all sides.
+ EdgeInsets get all => EdgeInsets.all(this);
+
+ /// Creates an `EdgeInsets` with symmetric horizontal padding (left and right).
+ EdgeInsets get horizontal => EdgeInsets.symmetric(horizontal: this);
+
+ /// Creates an `EdgeInsets` with symmetric vertical padding (top and bottom).
+ EdgeInsets get vertical => EdgeInsets.symmetric(vertical: this);
+}
+
+/// Extension on `double` to simplify the creation of `BorderRadius` and `Radius` objects.
+extension RadiusesDoubleExt on double {
+ /// Creates a `BorderRadius` with the specified corners rounded.
+ ///
+ /// You can round specific corners by setting the boolean flags:
+ /// `tl` (top-left), `tr` (top-right), `bl` (bottom-left), and `br` (bottom-right).
+ BorderRadius ronly({
+ bool tl = false,
+ bool tr = false,
+ bool bl = false,
+ bool br = false,
+ }) =>
+ BorderRadius.only(
+ topLeft: Radius.circular(tl ? this : 0),
+ topRight: Radius.circular(tr ? this : 0),
+ bottomLeft: Radius.circular(bl ? this : 0),
+ bottomRight: Radius.circular(br ? this : 0),
+ );
+
+ /// Creates a `BorderRadius` with only the top-left corner rounded.
+ BorderRadius get topLeft => BorderRadius.only(topLeft: Radius.circular(this));
+
+ /// Creates a `BorderRadius` with only the top-right corner rounded.
+ BorderRadius get topRight =>
+ BorderRadius.only(topRight: Radius.circular(this));
+
+ /// Creates a `BorderRadius` with only the bottom-left corner rounded.
+ BorderRadius get bottomLeft =>
+ BorderRadius.only(bottomLeft: Radius.circular(this));
+
+ /// Creates a `BorderRadius` with only the bottom-right corner rounded.
+ BorderRadius get bottomRight =>
+ BorderRadius.only(bottomRight: Radius.circular(this));
+
+ /// Creates a `BorderRadius` with symmetric horizontal corners rounded (left and right).
+ BorderRadius get rhorizontal => BorderRadius.horizontal(
+ left: Radius.circular(this), right: Radius.circular(this));
+
+ /// Creates a `BorderRadius` with symmetric vertical corners rounded (top and bottom).
+ BorderRadius get rvertical => BorderRadius.vertical(
+ top: Radius.circular(this), bottom: Radius.circular(this));
+
+ /// Creates a `BorderRadius` where all corners are rounded by the same amount.
+ BorderRadius get borderRadius => BorderRadius.all(Radius.circular(this));
+
+ /// Creates a `Radius` object with a circular radius equal to this value.
+ Radius get circularRadius => Radius.circular(this);
+}
diff --git a/infrastructure/lib/_core/commons/extensions/duration.ext.dart b/infrastructure/lib/_core/commons/extensions/duration.ext.dart
new file mode 100644
index 0000000..c56e33f
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/duration.ext.dart
@@ -0,0 +1,21 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:async';
+
+import 'package:infrastructure/_core/commons/typedefs/future_or_callback.typedef.dart';
+
+/// Extension on `Duration` to easily introduce delays with callbacks.
+extension DurationExt on Duration {
+ /// Delays the execution of the provided [callback] for the duration.
+ ///
+ /// The callback can either be synchronous or asynchronous.
+ Future delay([FutureOrCallback? callback]) =>
+ Future.delayed(
+ this,
+ callback,
+ );
+}
diff --git a/infrastructure/lib/_core/commons/extensions/generics.ext.dart b/infrastructure/lib/_core/commons/extensions/generics.ext.dart
new file mode 100644
index 0000000..eab1905
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/generics.ext.dart
@@ -0,0 +1,14 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Extension on nullable generic types to provide utility methods for null checks.
+extension GenericsExt on T? {
+ /// Returns `true` if the object is `null`.
+ bool get ifNull => this == null;
+
+ /// Returns `true` if the object is not `null`.
+ bool get ifNotNull => this != null;
+}
diff --git a/infrastructure/lib/_core/commons/extensions/int.ext.dart b/infrastructure/lib/_core/commons/extensions/int.ext.dart
new file mode 100644
index 0000000..2a9a990
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/int.ext.dart
@@ -0,0 +1,10 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Placeholder extension on `int` for future utility methods.
+extension IntExt on int {
+ // Add custom integer utility methods here as needed.
+}
diff --git a/infrastructure/lib/_core/commons/extensions/string.ext.dart b/infrastructure/lib/_core/commons/extensions/string.ext.dart
new file mode 100644
index 0000000..e1fc9aa
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/string.ext.dart
@@ -0,0 +1,101 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/widgets.dart';
+
+/// Extension on `String` to provide various text manipulation and utility methods.
+extension StringExt on String {
+ /// Capitalizes the first letter of each word in the string.
+ String get capitalize => isEmpty
+ ? this
+ : split(' ').map((String letter) => letter.capitalizeFirst).join(' ');
+
+ /// Capitalizes only the first letter of the string.
+ String get capitalizeFirst {
+ if (isEmpty) {
+ return this;
+ }
+ final String firstCharacter = characters.firstOrNull?.toUpperCase() ?? '';
+ final Characters remainingCharacters = characters.skip(1).toLowerCase();
+ return firstCharacter + remainingCharacters.string;
+ }
+
+ /// Converts the first letter of the string to lowercase.
+ String get lowercaseFirst {
+ if (isEmpty) {
+ return this;
+ }
+ final String firstCharacter = characters.firstOrNull?.toLowerCase() ?? '';
+ final Characters remainingCharacters = characters.skip(1);
+ return firstCharacter + remainingCharacters.string;
+ }
+
+ /// Calculates the width of the string when rendered with a specific [TextStyle].
+ double width(TextStyle style) {
+ final TextPainter textPainter = TextPainter(
+ text: TextSpan(text: this, style: style),
+ textDirection: TextDirection.ltr,
+ maxLines: 1,
+ )..layout();
+ return textPainter.size.width * 1.16;
+ }
+
+ /// Removes all whitespaces from the string.
+ String get removeAllWhitespace => replaceAll(' ', '');
+
+ /// Checks if the string matches a given regular expression [pattern].
+ bool hasMatch(String pattern) {
+ return RegExp(pattern).hasMatch(this);
+ }
+
+ /// Checks if the string contains only numeric characters.
+ bool get isNumericOnly => hasMatch(r'^\d+$');
+
+ /// Checks if the string contains only alphabetic characters.
+ bool get isAlphabetOnly => hasMatch(r'^[a-zA-Z]+$');
+
+ /// Checks if the string contains at least one capital letter.
+ bool get hasCapitalletter => hasMatch('[A-Z]');
+
+ /// Checks if the string represents a boolean value (`true` or `false`).
+ bool get isBool => this == 'true' || this == 'false';
+
+ /// Creates a `Text` widget with the string and applies the given [TextStyle] and other parameters.
+ Text text({
+ TextStyle? style,
+ Key? key,
+ StrutStyle? strutStyle,
+ TextAlign? textAlign,
+ TextDirection? textDirection,
+ Locale? locale,
+ bool? softWrap,
+ TextOverflow? overflow,
+ TextScaler? textScaler,
+ int? maxLines,
+ String? semanticsLabel,
+ TextWidthBasis? textWidthBasis,
+ TextHeightBehavior? textHeightBehavior,
+ Color? selectionColor,
+ }) {
+ return Text(
+ this,
+ key: key,
+ style: style,
+ strutStyle: strutStyle,
+ textAlign: textAlign,
+ textDirection: textDirection,
+ locale: locale,
+ softWrap: softWrap,
+ overflow: overflow,
+ textScaler: textScaler,
+ maxLines: maxLines,
+ semanticsLabel: semanticsLabel,
+ textWidthBasis: textWidthBasis,
+ textHeightBehavior: textHeightBehavior,
+ selectionColor: selectionColor,
+ );
+ }
+}
diff --git a/infrastructure/lib/_core/commons/extensions/text_style.ext.dart b/infrastructure/lib/_core/commons/extensions/text_style.ext.dart
new file mode 100644
index 0000000..1cd9f79
--- /dev/null
+++ b/infrastructure/lib/_core/commons/extensions/text_style.ext.dart
@@ -0,0 +1,72 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+
+/// Extension on `TextStyle` to easily modify text style properties.
+extension TextStyleExt on TextStyle {
+ /// Sets the font weight to the most thick (w900).
+ TextStyle get mostThick => copyWith(fontWeight: FontWeight.w900);
+
+ /// Sets the font weight to extra bold (w800).
+ TextStyle get extraBold => copyWith(fontWeight: FontWeight.w800);
+
+ /// Sets the font weight to bold (w700).
+ TextStyle get bold => copyWith(fontWeight: FontWeight.w700);
+
+ /// Sets the font weight to semi-bold (w600).
+ TextStyle get semiBold => copyWith(fontWeight: FontWeight.w600);
+
+ /// Sets the font weight to medium (w500).
+ TextStyle get medium => copyWith(fontWeight: FontWeight.w500);
+
+ /// Sets the font weight to regular (w400).
+ TextStyle get regular => copyWith(fontWeight: FontWeight.w400);
+
+ /// Sets the font weight to light (w300).
+ TextStyle get light => copyWith(fontWeight: FontWeight.w300);
+
+ /// Sets the font weight to extra light (w200).
+ TextStyle get extraLight => copyWith(fontWeight: FontWeight.w200);
+
+ /// Sets the font weight to thin (w100).
+ TextStyle get thin => copyWith(fontWeight: FontWeight.w100);
+
+ /// Sets the font style to italic.
+ TextStyle get italic => copyWith(fontStyle: FontStyle.italic);
+
+ /// Underlines the text.
+ TextStyle get underline => copyWith(decoration: TextDecoration.underline);
+
+ /// Changes the font size to the given [size].
+ TextStyle changeSize(double size) => copyWith(fontSize: size);
+
+ /// Changes the font family to the given [family].
+ TextStyle changeFamily(String family) => copyWith(fontFamily: family);
+
+ /// Changes the letter spacing to the given [space].
+ TextStyle changeLetterSpacing(double space) => copyWith(letterSpacing: space);
+
+ /// Changes the word spacing to the given [space].
+ TextStyle changeWordSpacing(double space) => copyWith(wordSpacing: space);
+
+ /// Changes the text color to the given [color].
+ TextStyle changeColor(Color color) => copyWith(color: color);
+
+ /// Changes the text baseline to the given [textBaseline].
+ TextStyle changeBaseline(TextBaseline textBaseline) =>
+ copyWith(textBaseline: textBaseline);
+
+ /// Changes the color of the text based on a [condition].
+ ///
+ /// If the [condition] is true, the color is set to [ifTrue]; otherwise, it's set to [ifFalse].
+ TextStyle changeColorWithCondition({
+ required bool condition,
+ Color? ifTrue,
+ Color? ifFalse,
+ }) =>
+ copyWith(color: condition ? ifTrue ?? color : ifFalse ?? color);
+}
diff --git a/infrastructure/lib/_core/commons/failures/parsing_failures.dart b/infrastructure/lib/_core/commons/failures/parsing_failures.dart
new file mode 100644
index 0000000..4f147e7
--- /dev/null
+++ b/infrastructure/lib/_core/commons/failures/parsing_failures.dart
@@ -0,0 +1,24 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:infrastructure/_core/commons/enums/failure_tag.enum.dart';
+import 'package:infrastructure/_core/commons/enums/failure_type.enum.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// A specific failure class for handling errors related to API response parsing.
+///
+/// This error occurs when an unexpected issue arises during the parsing of an API response
+/// into the expected output type.
+class ApiResponseParsingError extends Failure {
+ ApiResponseParsingError({super.exception, super.stack})
+ : super(
+ code: 'api-response-parsing-error',
+ type: FailureTypeEnum.error,
+ tag: FailureTagEnum.operation,
+ message:
+ 'An unexpected error occurred while parsing the API response for the desired (O)utput type.',
+ );
+}
diff --git a/infrastructure/lib/_core/commons/failures/unexpected_failures.dart b/infrastructure/lib/_core/commons/failures/unexpected_failures.dart
new file mode 100644
index 0000000..3b4b92a
--- /dev/null
+++ b/infrastructure/lib/_core/commons/failures/unexpected_failures.dart
@@ -0,0 +1,61 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:infrastructure/_core/commons/enums/failure_tag.enum.dart';
+import 'package:infrastructure/_core/commons/enums/failure_type.enum.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// A failure class for unexpected exceptions that do not fit into other categories.
+///
+/// This type of failure is used when an unexpected exception occurs during runtime.
+class UnexpectedFailure extends Failure {
+ UnexpectedFailure({super.exception})
+ : super(
+ code: 'unexpected-failure',
+ type: FailureTypeEnum.exception,
+ tag: FailureTagEnum.uncaught,
+ message: 'An unexpected exception occurred.',
+ );
+}
+
+/// A failure class for unexpected runtime errors that are not exceptions.
+///
+/// This type of error typically represents unexpected runtime issues that are not categorized as exceptions.
+class UnexpectedError extends Failure {
+ UnexpectedError({super.exception, super.stack})
+ : super(
+ code: 'unexpected-error',
+ type: FailureTypeEnum.error,
+ tag: FailureTagEnum.uncaught,
+ message: 'An unexpected error occurred.',
+ );
+}
+
+/// A failure class representing unexpected errors specifically within the Flutter framework.
+///
+/// This type of error occurs when a Flutter-specific issue arises during runtime.
+class UnexpectedFlutterError extends Failure {
+ UnexpectedFlutterError({super.exception, super.stack})
+ : super(
+ code: 'unexpected-flutter-error',
+ type: FailureTypeEnum.error,
+ tag: FailureTagEnum.uncaught,
+ message: 'An unexpected flutter error occurred.',
+ );
+}
+
+/// A failure class for unexpected errors that occur at the platform level.
+///
+/// This type of error occurs when there are platform-specific issues that disrupt normal operation.
+class UnexpectedPlatformError extends Failure {
+ UnexpectedPlatformError({super.exception, super.stack})
+ : super(
+ code: 'unexpected-platform-error',
+ type: FailureTypeEnum.error,
+ tag: FailureTagEnum.uncaught,
+ message: 'An unexpected platform error occurred.',
+ );
+}
diff --git a/infrastructure/lib/_core/commons/typedefs/either.typedef.dart b/infrastructure/lib/_core/commons/typedefs/either.typedef.dart
new file mode 100644
index 0000000..62d85e1
--- /dev/null
+++ b/infrastructure/lib/_core/commons/typedefs/either.typedef.dart
@@ -0,0 +1,38 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/fpdart.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// Type alias for an asynchronous operation that returns an `Either` type with
+/// a `Failure` on the left side (indicating an error) or a value of type `T`
+/// on the right side (indicating success).
+///
+/// This is commonly used in operations that perform asynchronous tasks such as
+/// network requests or database queries.
+///
+/// Example usage:
+/// ```dart
+/// AsyncEither fetchData() {
+/// // Implementation here
+/// }
+/// ```
+typedef AsyncEither = Future>;
+
+/// Type alias for a synchronous operation that returns an `Either` type with
+/// a `Failure` on the left side (indicating an error) or a value of type `T`
+/// on the right side (indicating success).
+///
+/// This is useful for operations that do not involve asynchronous tasks but
+/// can still fail and need to return a result or an error.
+///
+/// Example usage:
+/// ```dart
+/// SyncEither validateData() {
+/// // Implementation here
+/// }
+/// ```
+typedef SyncEither = Either;
diff --git a/infrastructure/lib/_core/commons/typedefs/future_or_callback.typedef.dart b/infrastructure/lib/_core/commons/typedefs/future_or_callback.typedef.dart
new file mode 100644
index 0000000..9dc89db
--- /dev/null
+++ b/infrastructure/lib/_core/commons/typedefs/future_or_callback.typedef.dart
@@ -0,0 +1,22 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:async';
+
+/// A type alias for a callback function that can return either a `Future` or
+/// a synchronous value of any type (`dynamic`).
+///
+/// This type is useful for situations where a callback might return either an
+/// asynchronous operation (like a `Future`) or a synchronous operation, allowing
+/// the function to handle both scenarios.
+///
+/// Example usage:
+/// ```dart
+/// FutureOrCallback myCallback = () async {
+/// // Do something asynchronously or synchronously
+/// };
+/// ```
+typedef FutureOrCallback = FutureOr Function()?;
diff --git a/infrastructure/lib/_core/commons/typedefs/future_void_callback.typedef.dart b/infrastructure/lib/_core/commons/typedefs/future_void_callback.typedef.dart
new file mode 100644
index 0000000..97d3c47
--- /dev/null
+++ b/infrastructure/lib/_core/commons/typedefs/future_void_callback.typedef.dart
@@ -0,0 +1,18 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// A type alias for a callback function that returns a `Future`.
+///
+/// This is useful for defining asynchronous functions that do not return any
+/// value but may still need to perform asynchronous operations.
+///
+/// Example usage:
+/// ```dart
+/// FutureVoidCallback myCallback = () async {
+/// // Perform some asynchronous operation
+/// };
+/// ```
+typedef FutureVoidCallback = Future Function();
diff --git a/infrastructure/lib/_core/commons/typedefs/model_from_json.typedef.dart b/infrastructure/lib/_core/commons/typedefs/model_from_json.typedef.dart
new file mode 100644
index 0000000..0db5bb8
--- /dev/null
+++ b/infrastructure/lib/_core/commons/typedefs/model_from_json.typedef.dart
@@ -0,0 +1,20 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// A type alias for a function that takes a `Map` (typically JSON)
+/// and returns an instance of a model of type `I`.
+///
+/// This is commonly used in deserialization, where a model object is constructed
+/// from a JSON representation.
+///
+/// Example usage:
+/// ```dart
+/// ModelFromJson fromJson = (json) => MyModel.fromJson(json);
+/// ```
+///
+/// In this example, `MyModel.fromJson` would be a factory constructor or a
+/// static method that takes a `Map` and returns a `MyModel` instance.
+typedef ModelFromJson = I Function(Map json);
diff --git a/infrastructure/lib/_core/commons/typedefs/widget_builder.typedef.dart b/infrastructure/lib/_core/commons/typedefs/widget_builder.typedef.dart
new file mode 100644
index 0000000..bc43c8d
--- /dev/null
+++ b/infrastructure/lib/_core/commons/typedefs/widget_builder.typedef.dart
@@ -0,0 +1,23 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:flutter/material.dart';
+
+/// A type alias for a function that builds a `Widget` using the provided `BuildContext`.
+///
+/// This is typically used in Flutter to create widgets dynamically, for example,
+/// in navigation, dialog builders, or list builders.
+///
+/// Example usage:
+/// ```dart
+/// WidgetBuilder myWidgetBuilder = (BuildContext context) {
+/// return Text('Hello, world!');
+/// };
+/// ```
+///
+/// This allows you to define widget-building functions that take in a `BuildContext`
+/// and return a widget, which is essential in many parts of Flutter’s widget tree.
+typedef WidgetBuilder = Widget Function(BuildContext context);
diff --git a/infrastructure/lib/analytics/failure/failure.dart b/infrastructure/lib/analytics/failure/failure.dart
new file mode 100644
index 0000000..49d9f94
--- /dev/null
+++ b/infrastructure/lib/analytics/failure/failure.dart
@@ -0,0 +1,99 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/infrastructure/infrastructure.dart';
+import 'package:deps/locator/locator.dart';
+import 'package:infrastructure/analytics/observers/i_failure_observer.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'failure.g.dart';
+
+/// `Failure` class represents an error or an exceptional case in the app.
+///
+/// It captures details such as the error code, message, tag, and type.
+/// Additionally, it can hold an exception and a stack trace for further analysis.
+@JsonSerializable()
+class Failure implements Exception {
+ Failure({
+ required this.code,
+ required this.message,
+ required this.tag,
+ required this.type,
+ dynamic exception,
+ this.stack,
+ }) : exception = (type == FailureTypeEnum.exception)
+ ? (exception ??
+ (throw ArgumentError(
+ 'Exception must be provided for FailureTypeEnum.exception.')))
+ : exception,
+ _failureObserver = locator() {
+ if (type == FailureTypeEnum.error) {
+ _failureObserver.onFailure(this);
+ }
+ }
+
+ /// Factory constructor for generating a `Failure` instance from JSON.
+ factory Failure.fromJson(Map json) =>
+ _$FailureFromJson(json);
+
+ /// Converts the current `Failure` instance to JSON.
+ Map toJson() => _$FailureToJson(this);
+
+ /// Factory constructor for creating an empty `Failure` instance.
+ factory Failure.empty() => Failure(
+ code: '',
+ message: '',
+ tag: FailureTagEnum.empty,
+ type: FailureTypeEnum.empty,
+ );
+
+ /// Error code related to the failure.
+ final String code;
+
+ /// Exception associated with the failure, if applicable.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ final dynamic exception;
+
+ /// Error message providing details about the failure.
+ final String message;
+
+ /// Stack trace associated with the failure.
+ @StackTraceConverter()
+ final StackTrace? stack;
+
+ /// Tag categorizing the failure.
+ final FailureTagEnum tag;
+
+ /// Type of failure (exception or error).
+ final FailureTypeEnum type;
+
+ /// Observer that handles failure events and logs them.
+ final IFailureObserver _failureObserver;
+
+ @override
+ String toString() => message;
+
+ /// Returns true if this failure instance is empty.
+ bool get isEmpty => type == FailureTypeEnum.empty;
+}
+
+/// Custom converter for serializing and deserializing `StackTrace` objects in JSON.
+class StackTraceConverter implements JsonConverter {
+ const StackTraceConverter();
+
+ @override
+ StackTrace? fromJson(String? json) {
+ if (json == null) {
+ return null;
+ }
+ return StackTrace.fromString(json);
+ }
+
+ @override
+ String? toJson(StackTrace? object) {
+ return object?.toString();
+ }
+}
diff --git a/infrastructure/lib/analytics/observers/failure_observer.dart b/infrastructure/lib/analytics/observers/failure_observer.dart
new file mode 100644
index 0000000..019b6bd
--- /dev/null
+++ b/infrastructure/lib/analytics/observers/failure_observer.dart
@@ -0,0 +1,50 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/injectable.dart';
+import 'package:infrastructure/_core/commons/enums/failure_type.enum.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+import 'package:infrastructure/analytics/observers/i_failure_observer.dart';
+import 'package:infrastructure/analytics/reporters/i_analytics.dart';
+import 'package:infrastructure/analytics/reporters/i_logger.dart';
+import 'package:infrastructure/flavors/i_env.dart';
+
+/// Implementation of `IFailureObserver` to log and report failures.
+/// This observer integrates with analytics and logging systems.
+@LazySingleton(as: IFailureObserver)
+class FailureObserver implements IFailureObserver {
+ const FailureObserver(this._analytics, this._env, this._logger);
+
+ /// Analytics service for reporting failures.
+ final IAnalytics _analytics;
+
+ /// Environment configuration to check if the app is in debug mode.
+ final IEnv _env;
+
+ /// Logger for logging error and exception details.
+ final ILogger _logger;
+
+ /// Handles failure based on its type and logs or reports it.
+ @override
+ void onFailure(Failure failure) {
+ // Only send to analytics if not in debug mode
+ if (!_env.isDebug) {
+ _analytics.send(failure.message);
+ }
+
+ // Log failure based on its type
+ switch (failure.type) {
+ case FailureTypeEnum.exception:
+ _logger.exception(failure);
+
+ case FailureTypeEnum.error:
+ _logger.error(failure);
+
+ default:
+ return;
+ }
+ }
+}
diff --git a/infrastructure/lib/analytics/observers/i_failure_observer.dart b/infrastructure/lib/analytics/observers/i_failure_observer.dart
new file mode 100644
index 0000000..046799c
--- /dev/null
+++ b/infrastructure/lib/analytics/observers/i_failure_observer.dart
@@ -0,0 +1,13 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// Interface for observing and responding to failure events.
+abstract class IFailureObserver {
+ /// Called when a failure occurs.
+ void onFailure(Failure failure);
+}
diff --git a/infrastructure/lib/analytics/observers/talker/bloc_talker_observer.dart b/infrastructure/lib/analytics/observers/talker/bloc_talker_observer.dart
new file mode 100644
index 0000000..635ed98
--- /dev/null
+++ b/infrastructure/lib/analytics/observers/talker/bloc_talker_observer.dart
@@ -0,0 +1,91 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:deps/packages/talker_bloc_logger.dart'
+ hide BlocChangeLog, BlocCloseLog, BlocCreateLog, BlocEventLog, BlocStateLog;
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:flutter/foundation.dart';
+import 'package:infrastructure/analytics/reporters/talker/logs/bloc_logs.dart';
+
+/// Custom `BlocObserver` that logs Bloc events, transitions, and errors using Talker.
+@immutable
+final class BlocTalkerObserver extends BlocObserver {
+ const BlocTalkerObserver({required this.settings, required this.talker});
+
+ /// Talker instance for logging Bloc events.
+ final Talker talker;
+
+ /// Settings for customizing Bloc logging.
+ final TalkerBlocLoggerSettings settings;
+
+ @override
+ @mustCallSuper
+ void onEvent(Bloc bloc, Object? event) {
+ super.onEvent(bloc, event);
+ if (!settings.enabled || !settings.printEvents) {
+ return;
+ }
+ final bool isAccepted = settings.eventFilter?.call(bloc, event) ?? true;
+ if (!isAccepted) {
+ return;
+ }
+ talker.logTyped(BlocEventLog(bloc: bloc, event: event, settings: settings));
+ }
+
+ @override
+ @mustCallSuper
+ void onTransition(
+ Bloc bloc, Transition transition) {
+ super.onTransition(bloc, transition);
+ if (!settings.enabled || !settings.printTransitions) {
+ return;
+ }
+ final bool isAccepted =
+ settings.transitionFilter?.call(bloc, transition) ?? true;
+ if (!isAccepted) {
+ return;
+ }
+ talker.logTyped(
+ BlocStateLog(bloc: bloc, settings: settings, transition: transition));
+ }
+
+ @override
+ void onChange(BlocBase bloc, Change change) {
+ super.onChange(bloc, change);
+ if (!settings.enabled || !settings.printChanges) {
+ return;
+ }
+ talker.logTyped(
+ BlocChangeLog(bloc: bloc, change: change, settings: settings),
+ );
+ }
+
+ @override
+ @mustCallSuper
+ void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
+ super.onError(bloc, error, stackTrace);
+ talker.error(bloc.runtimeType, error, stackTrace);
+ }
+
+ @override
+ void onCreate(BlocBase bloc) {
+ super.onCreate(bloc);
+ if (!settings.enabled || !settings.printCreations) {
+ return;
+ }
+ talker.logTyped(BlocCreateLog(bloc: bloc));
+ }
+
+ @override
+ void onClose(BlocBase bloc) {
+ super.onClose(bloc);
+ if (!settings.enabled || !settings.printClosings) {
+ return;
+ }
+ talker.logTyped(BlocCloseLog(bloc: bloc));
+ }
+}
diff --git a/infrastructure/lib/analytics/observers/talker/dio_talker_observer.dart b/infrastructure/lib/analytics/observers/talker/dio_talker_observer.dart
new file mode 100644
index 0000000..36dc214
--- /dev/null
+++ b/infrastructure/lib/analytics/observers/talker/dio_talker_observer.dart
@@ -0,0 +1,92 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/dio.dart';
+import 'package:deps/packages/talker_dio_logger.dart';
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:infrastructure/analytics/reporters/talker/logs/dio_logs.dart';
+
+/// Custom `Interceptor` for Dio to log HTTP requests, responses, and errors using Talker.
+final class DioTalkerObserver extends Interceptor {
+ DioTalkerObserver({required this.settings, required this.talker});
+
+ /// Talker instance for logging.
+ final Talker talker;
+
+ /// Settings for customizing Talker Dio logging.
+ TalkerDioLoggerSettings settings;
+
+ /// Allows for the configuration of Dio logger settings.
+ void configure({
+ AnsiPen? errorPen,
+ bool? printRequestData,
+ bool? printRequestHeaders,
+ bool? printResponseData,
+ bool? printResponseHeaders,
+ bool? printResponseMessage,
+ AnsiPen? requestPen,
+ AnsiPen? responsePen,
+ }) {
+ settings = settings.copyWith(
+ printResponseData: printResponseData,
+ printResponseHeaders: printResponseHeaders,
+ printResponseMessage: printResponseMessage,
+ printRequestData: printRequestData,
+ printRequestHeaders: printRequestHeaders,
+ requestPen: requestPen,
+ responsePen: responsePen,
+ errorPen: errorPen,
+ );
+ }
+
+ @override
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
+ super.onRequest(options, handler);
+ final bool accepted = settings.requestFilter?.call(options) ?? true;
+ if (!accepted) {
+ return;
+ }
+
+ final String message = '${options.uri}';
+ final DioRequestLog httpLog = DioRequestLog(
+ message,
+ requestOptions: options,
+ settings: settings,
+ );
+ talker.logTyped(httpLog);
+ }
+
+ @override
+ void onResponse(
+ Response response, ResponseInterceptorHandler handler) {
+ super.onResponse(response, handler);
+ final bool accepted = settings.responseFilter?.call(response) ?? true;
+ if (!accepted) {
+ return;
+ }
+
+ final String message = '${response.requestOptions.uri}';
+ final DioResponseLog httpLog = DioResponseLog(
+ message,
+ response: response,
+ settings: settings,
+ );
+ talker.logTyped(httpLog);
+ }
+
+ @override
+ void onError(DioException err, ErrorInterceptorHandler handler) {
+ super.onError(err, handler);
+
+ final String message = '${err.requestOptions.uri}';
+ final DioErrorLog httpErrorLog = DioErrorLog(
+ message,
+ dioException: err,
+ settings: settings,
+ );
+ talker.logTyped(httpErrorLog);
+ }
+}
diff --git a/infrastructure/lib/analytics/observers/talker/router_talker_observer.dart b/infrastructure/lib/analytics/observers/talker/router_talker_observer.dart
new file mode 100644
index 0000000..8e2ea45
--- /dev/null
+++ b/infrastructure/lib/analytics/observers/talker/router_talker_observer.dart
@@ -0,0 +1,37 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/talker_flutter.dart' hide TalkerRouteLog;
+import 'package:flutter/material.dart';
+import 'package:infrastructure/analytics/reporters/talker/logs/route_log.dart';
+
+/// Custom `NavigatorObserver` that logs route changes (push/pop) using Talker.
+class RouterTalkerObserver extends NavigatorObserver {
+ RouterTalkerObserver({required this.talker});
+
+ /// Instance of Talker used for logging route changes.
+ final Talker talker;
+
+ @override
+ void didPop(Route route, Route? previousRoute) {
+ super.didPop(route, previousRoute);
+ if (route.settings.name == null) {
+ return;
+ }
+ // Log when a route is popped
+ talker.logTyped(RouteLog(route: route, isPush: false));
+ }
+
+ @override
+ void didPush(Route route, Route? previousRoute) {
+ super.didPush(route, previousRoute);
+ if (route.settings.name == null) {
+ return;
+ }
+ // Log when a new route is pushed
+ talker.logTyped(RouteLog(route: route));
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/branch_io/branch_io_analytics.dart b/infrastructure/lib/analytics/reporters/branch_io/branch_io_analytics.dart
new file mode 100644
index 0000000..01eccb6
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/branch_io/branch_io_analytics.dart
@@ -0,0 +1,57 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/injectable.dart';
+import 'package:infrastructure/analytics/reporters/i_analytics.dart';
+
+/// A concrete implementation of the [IAnalytics] interface that uses Branch.io analytics.
+///
+/// The [BranchIoAnalytics] class provides methods for sending analytics data,
+/// tracking page views, and setting user IDs. This class is annotated with
+/// `@LazySingleton` to ensure that a single instance is used throughout the app.
+@LazySingleton(as: IAnalytics)
+class BranchIoAnalytics implements IAnalytics {
+ /// A constant constructor for the [BranchIoAnalytics] class.
+ ///
+ /// This ensures that the class is stateless and can be instantiated as a singleton.
+ const BranchIoAnalytics();
+
+ /// Sends an analytics message using Branch.io.
+ ///
+ /// This method is intended to send a message or event to the analytics system. In a
+ /// real implementation, this would involve interacting with Branch.io's API to log the event.
+ ///
+ /// * [message]: A string representing the event or message to be sent to analytics.
+ @override
+ Future send(String message) async {
+ // Add Branch.io event sending logic here.
+ }
+
+ /// Tracks a page view event using Branch.io.
+ ///
+ /// This method tracks when a user views a page in the app, providing information
+ /// about the page name and widget that triggered the event. Typically used for
+ /// navigation and screen tracking.
+ ///
+ /// * [name]: The name of the page viewed by the user.
+ /// * [widgetName]: The name of the widget associated with the page.
+ @override
+ Future setPage(
+ {required String name, required String widgetName}) async {
+ // Add Branch.io page view tracking logic here.
+ }
+
+ /// Sets the user ID in Branch.io for tracking purposes.
+ ///
+ /// This method assigns a unique identifier to a user in Branch.io, which allows
+ /// the analytics system to track events and actions tied to a specific user.
+ ///
+ /// * [id]: The unique user ID. If null, it could mean the user is logged out or anonymous.
+ @override
+ Future setUserId(String? id) async {
+ // Add Branch.io user ID setting logic here.
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/i_analytics.dart b/infrastructure/lib/analytics/reporters/i_analytics.dart
new file mode 100755
index 0000000..196c1ce
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/i_analytics.dart
@@ -0,0 +1,18 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Analytics interface for tracking user behavior and interactions.
+///
+/// - `setUserId`: Sets the user ID for analytics tracking.
+/// - `setPage`: Logs page navigation or views with a specific page name and widget.
+/// - `send`: Sends a custom analytics event or message.
+abstract interface class IAnalytics {
+ void setUserId(String? id);
+
+ void setPage({required String name, required String widgetName});
+
+ void send(String message);
+}
diff --git a/infrastructure/lib/analytics/reporters/i_logger.dart b/infrastructure/lib/analytics/reporters/i_logger.dart
new file mode 100755
index 0000000..17a9f4f
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/i_logger.dart
@@ -0,0 +1,20 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// Logger interface defining methods for handling different types of logs.
+///
+/// - `debug`: Logs debugging information.
+/// - `error`: Logs an error, usually caused by a `Failure`.
+/// - `exception`: Logs an exception, typically associated with a `Failure`.
+abstract class ILogger {
+ void debug(dynamic data, [String? message]);
+
+ void error(Failure failure);
+
+ void exception(Failure failure);
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/formatter/fancy_talker_log_formatter.dart b/infrastructure/lib/analytics/reporters/talker/formatter/fancy_talker_log_formatter.dart
new file mode 100644
index 0000000..1e82c9c
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/formatter/fancy_talker_log_formatter.dart
@@ -0,0 +1,93 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:flutter/material.dart';
+
+/// A fancy log formatter for `Talker` that adds more detailed borders and formatting to logs.
+///
+/// This formatter wraps log messages in a border with additional visual indicators
+/// like bullet points, and it adjusts the log format to be more visually distinct.
+class FancyTalkerLogFormatter implements LoggerFormatter {
+ @override
+ String fmt(LogDetails details, TalkerLoggerSettings settings) {
+ final String msg = details.message?.toString() ?? '';
+ final int maxLineWidth = settings.maxLineWidth;
+ final int maxContentWidth = maxLineWidth - 4;
+
+ const List noPrefixLabels = ['•', '«'];
+
+ final List coloredLines = [
+ for (final String line in msg.split('\n'))
+ for (final String wrappedLine in _wrapLine(
+ maxContentWidth,
+ line,
+ noPrefixKeywords: noPrefixLabels,
+ prefix: ' ' * 16,
+ ))
+ _formatLine(wrappedLine, maxContentWidth, details.pen),
+ ];
+
+ final String topBorder =
+ details.pen.write('┌${'─' * (maxLineWidth - 2)}┐\n');
+ final String bottomBorder =
+ details.pen.write('└${'─' * (maxLineWidth - 2)}┘');
+
+ return '$topBorder${coloredLines.join('\n')}\n$bottomBorder';
+ }
+
+ /// Formats a line of text to fit within a border.
+ String _formatLine(String line, int maxContentWidth, AnsiPen pen) {
+ final int paddingSize = maxContentWidth - line.length;
+ final String padding = ' ' * (paddingSize > 0 ? paddingSize : 0);
+
+ return pen.write('│ $line$padding │');
+ }
+
+ /// Wraps lines that exceed the specified width, with optional prefixes.
+ Iterable _wrapLine(
+ int maxContentWidth,
+ String text, {
+ List noPrefixKeywords = const [],
+ String prefix = '',
+ }) {
+ final List wrappedLines = [];
+ String currentLine = text.replaceAll('\t', ' ');
+
+ while (currentLine.isNotEmpty) {
+ int currentMaxWidth = maxContentWidth;
+
+ final bool isStartWithKeyword = noPrefixKeywords
+ .any((String keyword) => currentLine.startsWith(keyword));
+
+ if (wrappedLines.isNotEmpty || !isStartWithKeyword) {
+ currentMaxWidth -= prefix.length;
+ }
+
+ int cutPoint = currentLine.length > currentMaxWidth
+ ? currentMaxWidth
+ : currentLine.length;
+
+ if (cutPoint < currentLine.length) {
+ final int lastSpace = currentLine.characters
+ .getRange(0, cutPoint)
+ .toString()
+ .lastIndexOf(' ');
+ if (lastSpace > -1) {
+ cutPoint = lastSpace;
+ }
+ }
+
+ wrappedLines.add(
+ ((wrappedLines.isEmpty && isStartWithKeyword) ? '' : prefix) +
+ currentLine.characters.getRange(0, cutPoint).toString(),
+ );
+ currentLine = currentLine.characters.getRange(cutPoint).toString().trim();
+ }
+
+ return wrappedLines;
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/formatter/simple_talker_log_formatter.dart b/infrastructure/lib/analytics/reporters/talker/formatter/simple_talker_log_formatter.dart
new file mode 100644
index 0000000..1ecd397
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/formatter/simple_talker_log_formatter.dart
@@ -0,0 +1,55 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:flutter/material.dart';
+
+/// A simple log formatter for `Talker` that formats logs with consistent padding and borders.
+///
+/// This formatter adds a border around each log entry and formats lines to a fixed width
+/// by wrapping them when necessary. It ensures that logs are easily readable within a terminal.
+class SimpleTalkerLogFormatter implements LoggerFormatter {
+ @override
+ String fmt(LogDetails details, TalkerLoggerSettings settings) {
+ final String msg = details.message?.toString() ?? '';
+ final int maxLineWidth = settings.maxLineWidth;
+
+ final List coloredLines = [
+ for (final String line in msg.split('\n'))
+ for (final String wrappedLine in _wrapLine(maxLineWidth, line))
+ formattedLine(wrappedLine, maxLineWidth, details.pen),
+ ];
+
+ final String topBorder = details.pen.write('${'*' * maxLineWidth}\n');
+ final String bottomBorder = details.pen.write('*' * maxLineWidth);
+
+ return '$topBorder${coloredLines.join('\n')}\n$bottomBorder';
+ }
+
+ /// Formats a line of text to fit within a specific width.
+ String formattedLine(String line, int maxLineWidth, AnsiPen pen) {
+ final int paddingSize = maxLineWidth - line.length;
+ final String padding = ' ' * (paddingSize > 0 ? paddingSize : 0);
+
+ return pen.write('$line$padding');
+ }
+
+ /// Wraps lines that exceed the specified width, splitting them into multiple lines.
+ Iterable _wrapLine(int maxContentWidth, String text) {
+ final List wrappedLines = [];
+ String currentLine = text.replaceAll('\t', ' ');
+
+ while (currentLine.isNotEmpty) {
+ final int cutPoint = currentLine.length > maxContentWidth
+ ? maxContentWidth
+ : currentLine.length;
+ wrappedLines.add(currentLine.characters.getRange(0, cutPoint).toString());
+ currentLine = currentLine.characters.getRange(cutPoint).toString().trim();
+ }
+
+ return wrappedLines;
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/logs/bloc_logs.dart b/infrastructure/lib/analytics/reporters/talker/logs/bloc_logs.dart
new file mode 100644
index 0000000..d98ba0f
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/logs/bloc_logs.dart
@@ -0,0 +1,269 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+// ignore_for_file: avoid_dynamic_calls
+
+import 'package:deps/packages/flutter_bloc.dart';
+import 'package:deps/packages/intl.dart';
+import 'package:deps/packages/talker_bloc_logger.dart';
+import 'package:deps/packages/talker_flutter.dart';
+
+/// Custom log for logging Bloc events using Talker.
+///
+/// This class is responsible for formatting and logging events received by a Bloc,
+/// showing details like the Bloc's type, event received, and the time of the event.
+class BlocEventLog extends TalkerLog {
+ BlocEventLog({
+ required this.bloc,
+ required this.event,
+ required this.settings,
+ }) : super('');
+
+ final Bloc bloc;
+ final dynamic event;
+ final TalkerBlocLoggerSettings settings;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage;
+ }
+
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(51);
+
+ /// Generates a formatted message for logging the Bloc event.
+ String get _createMessage {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« BLOC on $_formatTime »')
+ ..writeln('• NAME\t ─► ${bloc.runtimeType}')
+ ..writeln(
+ '• STATUS\t─► Received event: ${settings.printEventFullData ? event : event.runtimeType}');
+ return stringBuffer.toString();
+ }
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+}
+
+/// Custom log for logging Bloc state transitions using Talker.
+///
+/// This class logs the details of a Bloc's state transition, including the
+/// current and next state, and the event that triggered the transition.
+class BlocStateLog extends TalkerLog {
+ BlocStateLog({
+ required this.bloc,
+ required this.settings,
+ required this.transition,
+ }) : super('');
+
+ final Bloc bloc;
+ final TalkerBlocLoggerSettings settings;
+ final Transition transition;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage;
+ }
+
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(49);
+
+ /// Generates a formatted message for logging the state transition.
+ String get _createMessage {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« BLOC on $_formatTime »')
+ ..writeln('• NAME\t ─► ${bloc.runtimeType}')
+ ..writeln(
+ '• STATUS\t─► Transitioning with event ${transition.event.runtimeType}');
+
+ final dynamic currentState = _serializeObject(transition.currentState);
+ final dynamic nextState = _serializeObject(transition.nextState);
+
+ if (settings.printStateFullData &&
+ (currentState is Map || currentState is List) &&
+ (nextState is Map || nextState is List)) {
+ if (settings.printStateFullData) {
+ stringBuffer
+ ..writeln(
+ '• FROM\t ─► ${currentState.entries.map(
+ (MapEntry entry) =>
+ '- "${entry.key}": "${entry.value}"',
+ ).join(',\n')}',
+ )
+ ..writeln(
+ '• TO\t\t─► ${nextState.entries.map(
+ (MapEntry entry) =>
+ '- "${entry.key}": "${entry.value}"',
+ ).join(',\n')}',
+ );
+ }
+ } else {
+ stringBuffer
+ ..writeln(
+ '• FROM\t ─► ${transition.currentState.runtimeType}',
+ )
+ ..writeln(
+ '• TO\t\t─► ${transition.nextState.runtimeType}',
+ );
+ }
+
+ return stringBuffer.toString();
+ }
+
+ /// Attempts to serialize the state object, or falls back to its string representation.
+ dynamic _serializeObject(dynamic object) {
+ if (object == null) {
+ return null;
+ }
+ try {
+ return object.toJson();
+ } catch (e) {
+ return object.toString();
+ }
+ }
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+}
+
+/// Custom log for logging Bloc state changes using Talker.
+///
+/// This class logs state changes within a Bloc, including the current and next state.
+class BlocChangeLog extends TalkerLog {
+ BlocChangeLog({
+ required this.bloc,
+ required this.change,
+ required this.settings,
+ }) : super('');
+
+ final BlocBase bloc;
+ final Change change;
+ final TalkerBlocLoggerSettings settings;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage;
+ }
+
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(49);
+
+ /// Generates a formatted message for logging the state change.
+ String get _createMessage {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« BLOC on $_formatTime »')
+ ..writeln('• NAME\t ─► ${bloc.runtimeType}')
+ ..writeln('• STATUS\t─► Changed');
+
+ final dynamic currentState = _serializeObject(change.currentState);
+ final dynamic nextState = _serializeObject(change.nextState);
+
+ if (settings.printStateFullData &&
+ (currentState is Map || currentState is List) &&
+ (nextState is Map || nextState is List)) {
+ if (settings.printStateFullData) {
+ stringBuffer
+ ..writeln(
+ '• FROM\t ─► ${currentState.entries.map(
+ (MapEntry entry) =>
+ '- "${entry.key}": "${entry.value}"',
+ ).join(',\n')}',
+ )
+ ..writeln(
+ '• TO\t\t─► ${nextState.entries.map(
+ (MapEntry entry) =>
+ '- "${entry.key}": "${entry.value}"',
+ ).join(',\n')}',
+ );
+ }
+ } else {
+ stringBuffer
+ ..writeln(
+ '• FROM\t ─► ${change.currentState.runtimeType}',
+ )
+ ..writeln(
+ '• TO\t\t─► ${change.nextState.runtimeType}',
+ );
+ }
+
+ return stringBuffer.toString();
+ }
+
+ /// Attempts to serialize the state object, or falls back to its string representation.
+ dynamic _serializeObject(dynamic object) {
+ if (object == null) {
+ return null;
+ }
+ try {
+ return object.toJson();
+ } catch (e) {
+ return object.toString();
+ }
+ }
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+}
+
+/// Custom log for logging Bloc creation using Talker.
+///
+/// This class logs when a Bloc is created, showing the Bloc's runtime type and the timestamp.
+class BlocCreateLog extends TalkerLog {
+ BlocCreateLog({required this.bloc}) : super('');
+
+ final BlocBase bloc;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage;
+ }
+
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(8);
+
+ /// Generates a formatted message for logging the creation of a Bloc.
+ String get _createMessage {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« BLOC on $_formatTime »')
+ ..writeln('• NAME\t ─► ${bloc.runtimeType}')
+ ..writeln('• STATUS\t─► Created');
+
+ return stringBuffer.toString();
+ }
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+}
+
+/// Custom log for logging Bloc closure using Talker.
+///
+/// This class logs when a Bloc is closed, showing the Bloc's runtime type and the timestamp.
+class BlocCloseLog extends TalkerLog {
+ BlocCloseLog({required this.bloc}) : super('');
+
+ final BlocBase bloc;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage;
+ }
+
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(13);
+
+ /// Generates a formatted message for logging the closure of a Bloc.
+ String get _createMessage {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« BLOC on $_formatTime »')
+ ..writeln('• NAME\t ─► ${bloc.runtimeType}')
+ ..writeln('• STATUS\t─► Closed');
+
+ return stringBuffer.toString();
+ }
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/logs/debug_log.dart b/infrastructure/lib/analytics/reporters/talker/logs/debug_log.dart
new file mode 100644
index 0000000..a3343d4
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/logs/debug_log.dart
@@ -0,0 +1,73 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/intl.dart';
+import 'package:deps/packages/talker_flutter.dart';
+
+/// A custom log class for handling debug-level logs using Talker.
+///
+/// The `DebugLog` class formats and logs debug information, including optional messages and data.
+/// It is used for logging runtime details that help developers trace the flow of the application
+/// during development or debugging sessions.
+class DebugLog extends TalkerLog {
+ DebugLog(this.data, [this.msg]) : super('');
+
+ /// Data to be logged, can be any type (e.g., a variable or complex object).
+ final dynamic data;
+
+ /// An optional message to provide additional context to the logged data.
+ final String? msg;
+
+ /// Generates a debug log message, including formatted data and optional message.
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createDebugLog();
+ }
+
+ /// Specifies the log level for this log, which is set to `debug`.
+ @override
+ LogLevel get logLevel => LogLevel.debug;
+
+ /// Specifies the pen (color) used for formatting the debug log in the terminal.
+ /// The color is set to a light gray (`xterm(15)`).
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(15);
+
+ /// Formats the current time as `HH:mm:ss.SSS` for inclusion in the log message.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates the formatted debug log message with data and optional message.
+ ///
+ /// This method formats the log message by including the data type, the data itself,
+ /// and an optional message. It also includes the timestamp to indicate when the log was generated.
+ String _createDebugLog() {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« DEBUG on $_formatTime »');
+
+ // If data is present, format and log the data and its type.
+ if (data != null) {
+ final String formattedData = _formatData(data);
+ stringBuffer
+ ..writeln('• TYPE\t ─► ${data.runtimeType}')
+ ..writeln('• DATA\t ─► $formattedData');
+ }
+
+ // If a message is provided, include it in the log output.
+ if (msg != null) {
+ stringBuffer.writeln('• MESSAGE ─► $msg');
+ }
+
+ return stringBuffer.toString();
+ }
+
+ /// Formats the data into a readable string.
+ ///
+ /// If the data is an iterable (e.g., a list or set), it joins the elements
+ /// with commas. Otherwise, it converts the data to a string.
+ String _formatData(dynamic value) {
+ return value is Iterable ? value.join(', ') : data.toString();
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/logs/dio_logs.dart b/infrastructure/lib/analytics/reporters/talker/logs/dio_logs.dart
new file mode 100644
index 0000000..03d76ee
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/logs/dio_logs.dart
@@ -0,0 +1,284 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:convert';
+
+import 'package:deps/packages/dio.dart';
+import 'package:deps/packages/intl.dart';
+import 'package:deps/packages/talker_dio_logger.dart';
+import 'package:deps/packages/talker_flutter.dart';
+
+/// Converts common HTTP status codes to a descriptive emoji string.
+///
+/// This helper function maps HTTP status codes to human-readable descriptions
+/// to make logs more informative. If a status code isn't mapped, it returns the code itself.
+String statusCodeToEmoji(int statusCode) {
+ const Map statusCodeEmojis = {
+ 200: '(OK)',
+ 201: '(Created)',
+ 400: '(Bad Request)',
+ 401: '(Unauthorized)',
+ 403: '(Forbidden)',
+ 404: '(Not Found)',
+ 408: '(Timeout)',
+ 429: '(Too Many Requests)',
+ 500: '(Internal Server Error)',
+ 503: '(Service Unavailable)',
+ };
+
+ final String? emojiDescription = statusCodeEmojis[statusCode];
+ if (emojiDescription != null) {
+ return '$statusCode - $emojiDescription';
+ } else {
+ return '$statusCode';
+ }
+}
+
+/// Custom log for logging `Dio` HTTP requests using Talker.
+///
+/// This class captures and formats the details of an HTTP request made via `Dio`.
+/// It logs the request method, URL, headers, and body if available, making it easier
+/// to track outgoing network requests.
+class DioRequestLog extends TalkerLog {
+ DioRequestLog(
+ this.uri, {
+ required this.requestOptions,
+ required this.settings,
+ }) : super('');
+
+ final RequestOptions requestOptions;
+ final TalkerDioLoggerSettings settings;
+ final String uri;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage();
+ }
+
+ @override
+ AnsiPen get pen => settings.requestPen ?? (AnsiPen()..xterm(219));
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates a formatted log message for the HTTP request, including method, URL, headers, and body data.
+ String _createMessage() {
+ const JsonEncoder encoder = JsonEncoder.withIndent(' ');
+ final dynamic data = requestOptions.data;
+ final Map headers = requestOptions.headers;
+
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« DIO on $_formatTime »')
+ ..writeln('• TYPE\t ─► ${requestOptions.method} Request')
+ ..writeln('• URL\t ─► $uri');
+
+ // Log request headers if enabled.
+ if (settings.printRequestHeaders && headers.isNotEmpty) {
+ final Map stringHeaders = headers.map(
+ (String key, dynamic value) =>
+ MapEntry(key, value.toString()),
+ );
+
+ final String prettyHeaders = encoder.convert(stringHeaders);
+ final Map parsedJson = jsonDecode(prettyHeaders);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• HEADERS ─► $formattedEntries');
+ }
+
+ // Log request data if enabled.
+ if (settings.printRequestData && data != null) {
+ final String prettyData = encoder.convert(data);
+ final Map parsedJson = jsonDecode(prettyData);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• DATA\t ─► $formattedEntries');
+ }
+
+ return stringBuffer.toString();
+ }
+}
+
+/// Custom log for logging `Dio` HTTP responses using Talker.
+///
+/// This class captures and formats the details of an HTTP response received via `Dio`.
+/// It logs the response status, headers, and body data, making it easier to track incoming network responses.
+class DioResponseLog extends TalkerLog {
+ DioResponseLog(this.uri, {required this.response, required this.settings})
+ : super('');
+
+ final Response response;
+ final TalkerDioLoggerSettings settings;
+ final String uri;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage();
+ }
+
+ @override
+ AnsiPen get pen => settings.responsePen ?? (AnsiPen()..xterm(46));
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates a formatted log message for the HTTP response, including status, headers, and body data.
+ String _createMessage() {
+ const JsonEncoder encoder = JsonEncoder.withIndent(' ');
+ final String? responseMessage = response.statusMessage;
+ final dynamic data = response.data;
+ final Map> headers = response.headers.map;
+
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« DIO on $_formatTime »')
+ ..writeln('• TYPE\t ─► ${response.requestOptions.method} Response')
+ ..writeln('• URL\t ─► $uri');
+
+ // Log status code with description.
+ if (response.statusCode != null) {
+ final String statusWithEmoji = statusCodeToEmoji(response.statusCode!);
+ stringBuffer.writeln('• STATUS\t─► $statusWithEmoji');
+ }
+
+ // Log response message if available.
+ if (settings.printResponseMessage && responseMessage != null) {
+ stringBuffer.writeln('• MESSAGE ─► $responseMessage');
+ }
+
+ // Log response headers if enabled.
+ if (settings.printRequestHeaders && headers.isNotEmpty) {
+ final Map stringHeaders = headers.map(
+ (String key, dynamic value) =>
+ MapEntry(key, value.toString()),
+ );
+
+ final String prettyHeaders = encoder.convert(stringHeaders);
+ final Map parsedJson = jsonDecode(prettyHeaders);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• HEADERS ─► $formattedEntries');
+ }
+
+ // Log response data if enabled.
+ if (settings.printResponseData && data != null) {
+ final String prettyData = encoder.convert(data);
+ if (data is Map) {
+ final Map parsedJson = jsonDecode(prettyData);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• DATA\t ─► $formattedEntries');
+ } else if (data is List) {
+ final List parsedJson = jsonDecode(prettyData);
+ final String formattedEntries =
+ parsedJson.map((dynamic entry) => '- $entry').join('\n');
+ stringBuffer.writeln('• DATA\t ─► $formattedEntries');
+ }
+ }
+
+ return stringBuffer.toString();
+ }
+}
+
+/// Custom log for logging `Dio` HTTP errors using Talker.
+///
+/// This class captures and formats the details of an HTTP error encountered via `Dio`.
+/// It logs the error status, headers, and any data, making it easier to debug failed network requests.
+class DioErrorLog extends TalkerLog {
+ DioErrorLog(
+ this.uri, {
+ required this.dioException,
+ required this.settings,
+ }) : super('');
+
+ final DioException dioException;
+ final TalkerDioLoggerSettings settings;
+ final String uri;
+
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage();
+ }
+
+ @override
+ AnsiPen get pen => settings.errorPen ?? (AnsiPen()..red());
+
+ /// Formats the current time as `HH:mm:ss.SSS` for log entry.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates a formatted log message for the HTTP error, including status, headers, and data.
+ String _createMessage() {
+ const JsonEncoder encoder = JsonEncoder.withIndent(' ');
+ final String? responseMessage = dioException.message;
+ final int? statusCode = dioException.response?.statusCode;
+ final dynamic data = dioException.response?.data;
+ final Map headers = dioException.requestOptions.headers;
+
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« DIO on $_formatTime »')
+ ..writeln('• TYPE\t ─► ${dioException.requestOptions.method} Error')
+ ..writeln('• URL\t ─► $uri');
+
+ // Log status code with description.
+ if (statusCode != null) {
+ final String statusWithEmoji = statusCodeToEmoji(statusCode);
+ stringBuffer.writeln('• STATUS\t─► $statusWithEmoji');
+ }
+
+ // Log response message if available.
+ if (responseMessage != null) {
+ stringBuffer.writeln('• MESSAGE ─► $responseMessage');
+ }
+
+ // Log request headers if enabled.
+ if (settings.printRequestHeaders && headers.isNotEmpty) {
+ final Map stringHeaders = headers.map(
+ (String key, dynamic value) =>
+ MapEntry(key, value.toString()),
+ );
+
+ final String prettyHeaders = encoder.convert(stringHeaders);
+ final Map parsedJson = jsonDecode(prettyHeaders);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• HEADERS ─► $formattedEntries');
+ }
+
+ // Log response data if available.
+ if (data != null) {
+ final String prettyData = encoder.convert(data);
+ final Map parsedJson = jsonDecode(prettyData);
+ final String formattedEntries = parsedJson.entries
+ .map(
+ (MapEntry entry) =>
+ '"${entry.key}": "${entry.value}"',
+ )
+ .join(',\n');
+ stringBuffer.writeln('• DATA\t ─► $formattedEntries');
+ }
+
+ return stringBuffer.toString();
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/logs/failure_log.dart b/infrastructure/lib/analytics/reporters/talker/logs/failure_log.dart
new file mode 100644
index 0000000..1e9e17b
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/logs/failure_log.dart
@@ -0,0 +1,70 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/intl.dart';
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:infrastructure/_core/commons/enums/failure_type.enum.dart';
+import 'package:infrastructure/_core/commons/extensions/string.ext.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+
+/// A custom log class for handling and logging system failures using Talker.
+///
+/// The `FailureLog` class formats and logs failure information, including the failure type,
+/// code, message, and optionally an exception if one is provided. It distinguishes between
+/// different levels of severity, setting the log level to either `warning` or `critical`
+/// based on the type of failure.
+class FailureLog extends TalkerLog {
+ FailureLog(this.failure) : super('');
+
+ /// The `Failure` instance that contains the details of the failure to be logged.
+ final Failure failure;
+
+ /// Generates a log message for the failure, including relevant details such as
+ /// the failure code, message, tag, and exception if present.
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage();
+ }
+
+ /// Determines the log level based on the failure type.
+ ///
+ /// If the failure is an exception, the log level is set to `warning`.
+ /// For other types of failures, the log level is set to `critical`.
+ @override
+ LogLevel get logLevel => failure.type == FailureTypeEnum.exception
+ ? LogLevel.warning
+ : LogLevel.critical;
+
+ /// Sets the color of the log output based on the failure type.
+ ///
+ /// Exceptions are displayed in an orange-like color (`xterm(208)`), while other failures
+ /// are shown in red (`xterm(196)`).
+ @override
+ AnsiPen get pen =>
+ AnsiPen()..xterm(failure.type == FailureTypeEnum.exception ? 208 : 196);
+
+ /// Formats the current time as `HH:mm:ss.SSS` for inclusion in the log message.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates the log message with failure details, including the type of failure,
+ /// tag, code, message, and exception if available.
+ String _createMessage() {
+ final String type =
+ failure.type == FailureTypeEnum.exception ? 'EXCEPTION' : 'FAILURE';
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« $type on $_formatTime »')
+ ..writeln('• TAG ─► ${failure.tag.name.capitalize}')
+ ..writeln('• CODE ─► ${failure.code}')
+ ..writeln('• MESSAGE ─► ${failure.message}');
+
+ // If an exception is present, include it in the log.
+ if (failure.exception != null) {
+ stringBuffer.writeln('• EXCEPTION ─► ${failure.exception}');
+ }
+
+ return stringBuffer.toString();
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/logs/route_log.dart b/infrastructure/lib/analytics/reporters/talker/logs/route_log.dart
new file mode 100644
index 0000000..57f0a92
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/logs/route_log.dart
@@ -0,0 +1,57 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/intl.dart';
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:flutter/material.dart';
+
+/// A custom log class for handling and logging route changes using Talker.
+///
+/// The `RouteLog` class formats and logs details about route changes (push and pop events)
+/// in a Flutter app's navigation stack. It logs the route name, status (whether the route
+/// was pushed or popped), and any arguments passed to the route.
+class RouteLog extends TalkerLog {
+ RouteLog({required this.route, this.isPush = true}) : super('');
+
+ /// Indicates whether the route was pushed or popped.
+ ///
+ /// If `true`, the route was pushed onto the navigation stack. If `false`, the route was popped off.
+ final bool isPush;
+
+ /// The route that was pushed or popped.
+ final Route route;
+
+ /// Generates the log message for the route event, including route name, status, and arguments.
+ @override
+ String generateTextMessage({TimeFormat? timeFormat}) {
+ return _createMessage();
+ }
+
+ /// Specifies the color of the log output.
+ ///
+ /// The color is set to a light green (`xterm(153)`).
+ @override
+ AnsiPen get pen => AnsiPen()..xterm(153);
+
+ /// Formats the current time as `HH:mm:ss.SSS` for inclusion in the log message.
+ String get _formatTime => DateFormat('HH:mm:ss.SSS').format(DateTime.now());
+
+ /// Creates the log message with route details, including the name, status, and any arguments passed to the route.
+ String _createMessage() {
+ final StringBuffer stringBuffer = StringBuffer()
+ ..writeln('\n« ROUTER on $_formatTime »')
+ ..writeln('• NAME\t ─► ${route.settings.name ?? 'null'}')
+ ..writeln('• STATUS\t─► ${isPush ? 'Pushed' : 'Popped'}');
+
+ // If arguments were passed to the route, include them in the log.
+ final Object? args = route.settings.arguments;
+ if (args != null) {
+ stringBuffer.write('• ARGS\t ─► $args');
+ }
+
+ return stringBuffer.toString();
+ }
+}
diff --git a/infrastructure/lib/analytics/reporters/talker/talker_logger.dart b/infrastructure/lib/analytics/reporters/talker/talker_logger.dart
new file mode 100644
index 0000000..dd32b11
--- /dev/null
+++ b/infrastructure/lib/analytics/reporters/talker/talker_logger.dart
@@ -0,0 +1,42 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/injectable.dart';
+import 'package:deps/packages/talker_flutter.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+import 'package:infrastructure/analytics/reporters/i_logger.dart';
+import 'package:infrastructure/analytics/reporters/talker/logs/debug_log.dart';
+import 'package:infrastructure/analytics/reporters/talker/logs/failure_log.dart';
+
+/// A concrete implementation of the `ILogger` interface using `Talker` for logging.
+///
+/// This logger supports logging debug information, errors, and exceptions
+/// through the `Talker` package. It wraps the `Talker` functionality with a
+/// custom log type for each logging level (e.g., `DebugLog`, `FailureLog`).
+@LazySingleton(as: ILogger)
+class TalkerLogger implements ILogger {
+ const TalkerLogger(this._talker);
+
+ final Talker _talker;
+
+ /// Logs debug information with an optional message.
+ @override
+ void debug(dynamic data, [String? message]) {
+ _talker.logTyped(DebugLog(data, message));
+ }
+
+ /// Logs an error using the `Failure` log type.
+ @override
+ void error(Failure failure) {
+ _talker.logTyped(FailureLog(failure));
+ }
+
+ /// Logs an exception using the `Failure` log type.
+ @override
+ void exception(Failure failure) {
+ _talker.logTyped(FailureLog(failure));
+ }
+}
diff --git a/infrastructure/lib/flavors/dev.env.dart b/infrastructure/lib/flavors/dev.env.dart
new file mode 100644
index 0000000..2c1df97
--- /dev/null
+++ b/infrastructure/lib/flavors/dev.env.dart
@@ -0,0 +1,38 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/envied.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:infrastructure/flavors/i_env.dart';
+
+part 'dev.env.g.dart';
+
+/// Development environment implementation of the `IEnv` interface.
+/// This class fetches its configuration from the `../.env.dev` file.
+///
+/// The `Envied` annotation is used to load the environment variables
+/// from the `.env.dev` file into the fields defined in this class.
+@Environment('dev')
+@Singleton(as: IEnv)
+@Envied(path: '../.env.dev')
+class DevEnv implements IEnv {
+ /// Indicates whether the development environment is in debug mode.
+ /// Set to `true` for debugging purposes.
+ @override
+ final bool isDebug = true;
+
+ /// URL for analytics services in the development environment.
+ /// This value is fetched from the `ANALYTICS_URL` variable in the `.env.dev` file.
+ @override
+ @EnviedField(varName: 'ANALYTICS_URL')
+ final String analyticsUrl = _DevEnv.analyticsUrl;
+
+ /// API base URL for the development environment.
+ /// This value is fetched from the `API_URL` variable in the `.env.dev` file.
+ @override
+ @EnviedField(varName: 'API_URL')
+ final String apiUrl = _DevEnv.apiUrl;
+}
diff --git a/infrastructure/lib/flavors/i_env.dart b/infrastructure/lib/flavors/i_env.dart
new file mode 100644
index 0000000..a98fc42
--- /dev/null
+++ b/infrastructure/lib/flavors/i_env.dart
@@ -0,0 +1,19 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+/// Interface that defines the environment configuration.
+/// This class is designed to be implemented by different
+/// environment-specific configurations (e.g., `EnvProd`, `EnvDev`).
+abstract interface class IEnv {
+ /// API base URL for the environment.
+ abstract final String apiUrl;
+
+ /// URL for analytics services for the environment.
+ abstract final String analyticsUrl;
+
+ /// Indicates whether the environment is in debug mode.
+ abstract final bool isDebug;
+}
diff --git a/infrastructure/lib/flavors/prod.env.dart b/infrastructure/lib/flavors/prod.env.dart
new file mode 100644
index 0000000..f942eff
--- /dev/null
+++ b/infrastructure/lib/flavors/prod.env.dart
@@ -0,0 +1,38 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'package:deps/packages/envied.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:infrastructure/flavors/i_env.dart';
+
+part 'prod.env.g.dart';
+
+/// Production environment implementation of the `IEnv` interface.
+/// This class fetches its configuration from the `../.env.prod` file.
+///
+/// The `Envied` annotation is used to load the environment variables
+/// from the `.env.prod` file into the fields defined in this class.
+@Environment('prod')
+@Singleton(as: IEnv)
+@Envied(path: '../.env.prod')
+class ProdEnv implements IEnv {
+ /// Indicates whether the production environment is in debug mode.
+ /// Set to `true` for debugging purposes in this example.
+ @override
+ final bool isDebug = true;
+
+ /// URL for analytics services in the production environment.
+ /// This value is fetched from the `ANALYTICS_URL` variable in the `.env.prod` file.
+ @override
+ @EnviedField(varName: 'ANALYTICS_URL')
+ final String analyticsUrl = _ProdEnv.analyticsUrl;
+
+ /// API base URL for the production environment.
+ /// This value is fetched from the `API_URL` variable in the `.env.prod` file.
+ @override
+ @EnviedField(varName: 'API_URL')
+ final String apiUrl = _ProdEnv.apiUrl;
+}
diff --git a/infrastructure/lib/infrastructure.dart b/infrastructure/lib/infrastructure.dart
new file mode 100644
index 0000000..b8473c6
--- /dev/null
+++ b/infrastructure/lib/infrastructure.dart
@@ -0,0 +1,69 @@
+export '_core/_router/router.dart';
+export '_core/_router/router.gr.dart';
+export '_core/commons/enums/auth_status.enum.dart';
+export '_core/commons/enums/connectivity_status.enum.dart';
+export '_core/commons/enums/env.enum.dart';
+export '_core/commons/enums/failure_tag.enum.dart';
+export '_core/commons/enums/failure_type.enum.dart';
+export '_core/commons/enums/log_type.enum.dart';
+export '_core/commons/enums/request_type.enum.dart';
+export '_core/commons/extensions/color.ext.dart';
+export '_core/commons/extensions/context.ext.dart';
+export '_core/commons/extensions/date_time.ext.dart';
+export '_core/commons/extensions/double.ext.dart';
+export '_core/commons/extensions/duration.ext.dart';
+export '_core/commons/extensions/generics.ext.dart';
+export '_core/commons/extensions/int.ext.dart';
+export '_core/commons/extensions/string.ext.dart';
+export '_core/commons/extensions/text_style.ext.dart';
+export '_core/commons/failures/unexpected_failures.dart';
+export '_core/commons/typedefs/either.typedef.dart';
+export 'analytics/failure/failure.dart';
+export 'analytics/observers/talker/bloc_talker_observer.dart';
+export 'analytics/observers/talker/dio_talker_observer.dart';
+export 'analytics/observers/talker/router_talker_observer.dart';
+export 'analytics/reporters/i_analytics.dart';
+export 'analytics/reporters/i_logger.dart';
+export 'flavors/i_env.dart';
+export 'networking/api_client/dio/dio_token_refresh.dart';
+export 'networking/api_client/i_api_client.dart';
+export 'networking/connectivity/connectivity.cubit.dart';
+export 'networking/failures/network_failures.dart';
+export 'networking/models/token.model.dart';
+export 'presentation/_core/dialog/dialog_config.dart';
+export 'presentation/_core/modal/modal_config.dart';
+export 'presentation/_core/sheet/sheet_config.dart';
+export 'presentation/contexts/dialog_context.dart';
+export 'presentation/contexts/overlay_context.dart';
+export 'presentation/contexts/toast_context.dart';
+export 'presentation/cubits/paginated_list.cubit.dart';
+export 'presentation/extensions/styled_text.ext.dart';
+export 'presentation/main_binding.dart';
+export 'presentation/models/paginated.model.dart';
+export 'presentation/super_class.dart';
+export 'presentation/validators/reactive_validators.dart';
+export 'presentation/widgets/_core/paddings/padding_all.dart';
+export 'presentation/widgets/_core/paddings/padding_bottom.dart';
+export 'presentation/widgets/_core/paddings/padding_gap.dart';
+export 'presentation/widgets/_core/paddings/padding_left.dart';
+export 'presentation/widgets/_core/paddings/padding_only.dart';
+export 'presentation/widgets/_core/paddings/padding_right.dart';
+export 'presentation/widgets/_core/paddings/padding_symmetric.dart';
+export 'presentation/widgets/_core/paddings/padding_top.dart';
+export 'presentation/widgets/_core/radiuses/radius_all.dart';
+export 'presentation/widgets/_core/radiuses/radius_horizontal.dart';
+export 'presentation/widgets/_core/radiuses/radius_only.dart';
+export 'presentation/widgets/_core/radiuses/radius_vertical.dart';
+export 'presentation/widgets/animated_box_decoration/smooth_animated_container.dart';
+export 'presentation/widgets/animated_box_decoration/smooth_decoration_tween.dart';
+export 'presentation/widgets/blend_mask.dart';
+export 'presentation/widgets/nil.dart';
+export 'presentation/widgets/paginated_list/paginated_list.dart';
+export 'presentation/widgets/transparent_pointer.dart';
+export 'presentation/widgets/widget_fader.dart';
+export 'presentation/wrappers/app_wrapper.dart';
+export 'storage/caches/i_cache.dart';
+export 'storage/file_storage/i_file_storage.dart';
+export 'storage/file_storage/token/secure_token_file_storage.dart';
+export 'storage/file_storage/token/token_file_storage_mixin.dart';
+export 'translations/translations.cubit.dart';
diff --git a/infrastructure/lib/networking/api_client/dio/dio_client.dart b/infrastructure/lib/networking/api_client/dio/dio_client.dart
new file mode 100644
index 0000000..ec31543
--- /dev/null
+++ b/infrastructure/lib/networking/api_client/dio/dio_client.dart
@@ -0,0 +1,185 @@
+// Copyright 2024 Thomas Hoang
+//
+// This source code is licensed under the MIT License found in the
+// LICENSE file.
+//
+
+import 'dart:io';
+
+import 'package:deps/packages/dio.dart';
+import 'package:deps/packages/dio_smart_retry.dart';
+import 'package:deps/packages/fpdart.dart';
+import 'package:deps/packages/injectable.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:infrastructure/_core/commons/enums/request_type.enum.dart';
+import 'package:infrastructure/_core/commons/failures/parsing_failures.dart';
+import 'package:infrastructure/_core/commons/failures/unexpected_failures.dart';
+import 'package:infrastructure/_core/commons/typedefs/either.typedef.dart';
+import 'package:infrastructure/_core/commons/typedefs/model_from_json.typedef.dart';
+import 'package:infrastructure/analytics/failure/failure.dart';
+import 'package:infrastructure/analytics/reporters/i_logger.dart';
+import 'package:infrastructure/networking/api_client/dio/dio_failure.dart';
+import 'package:infrastructure/networking/api_client/dio/dio_token_refresh.dart';
+import 'package:infrastructure/networking/api_client/dio/interceptors/dio_http_failures_interceptor.dart';
+import 'package:infrastructure/networking/api_client/dio/interceptors/dio_other_failures_interceptor.dart';
+import 'package:infrastructure/networking/api_client/i_api_client.dart';
+import 'package:infrastructure/networking/models/token.model.dart';
+import 'package:infrastructure/presentation/models/paginated.model.dart';
+import 'package:infrastructure/storage/file_storage/token/token_file_storage_mixin.dart';
+
+@LazySingleton(as: IApiClient)
+class DioClient implements IApiClient {
+ DioClient(this._dio, this._logger, this._tokenRefresh) {
+ _dio
+ ..options.headers['Accept-Language'] =
+ kIsWeb ? 'en-US' : Platform.localeName.characters.getRange(0, 2)
+ ..options.connectTimeout = const Duration(seconds: 5)
+ ..options.receiveTimeout = const Duration(seconds: 5)
+ ..interceptors.add(_tokenRefresh.interceptor)
+ ..interceptors.add(const DioHttpFailuresInterceptor())
+ ..interceptors.add(const DioOtherFailuresInterceptor())
+ ..interceptors.add(
+ RetryInterceptor(dio: _dio, logPrint: _logger.debug, retries: 2),
+ );
+ }
+
+ final Dio _dio;
+
+ final ILogger _logger;
+
+ final DioTokenRefresh _tokenRefresh;
+
+ @override
+ AsyncEither invoke(
+ String path,
+ RequestTypeEnum requestType, {
+ ModelFromJson? fromJson,
+ Map? queryParameters,
+ Map? data,
+ }) async {
+ Response response;
+
+ final bool isMbasicType = M.toString() == 'String' ||
+ M.toString() == 'double' ||
+ M.toString() == 'int' ||
+ M.toString() == 'bool';
+ final bool isSMbasicType = SM.toString() == 'String' ||
+ SM.toString() == 'double' ||
+ SM.toString() == 'int' ||
+ SM.toString() == 'bool';
+ final bool isMCollection = M.toString().startsWith('List<') ||
+ M.toString().startsWith('PaginatedModel<');
+ final bool isSMCollection = SM.toString().startsWith('List<') ||
+ SM.toString().startsWith('PaginatedModel<');
+
+ assert(
+ M != Unit && !isMbasicType && fromJson != null,
+ 'When (M)odel is not void and not a basic type then fromJson cannot be null.',
+ );
+
+ assert(
+ !isMCollection || SM.toString() != 'void',
+ 'When (M)odel is a List or PaginatedModel, then (S)ingle (M)odel cannot be void.',
+ );
+
+ assert(
+ !isMCollection || (SM.toString() != 'void' && !isSMCollection),
+ 'When (M)odel is a List