├── .editorconfig
├── .env.example
├── .github
└── dependabot.yml
├── .gitignore
├── .kiro
├── settings
│ └── mcp.json
└── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ └── taskmaster.md
├── .metadata
├── .taskmaster
├── config.json
├── docs
│ └── prd.txt
├── state.json
├── tasks
│ ├── task_001.txt
│ ├── task_002.txt
│ ├── task_003.txt
│ └── tasks.json
└── templates
│ └── example_prd.txt
├── AGENTS.md
├── GEMINI.md
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ ├── build.gradle.kts
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ ├── firebase_auth_flutter_ddd
│ │ │ │ └── MainActivity.kt
│ │ │ │ └── firebase_authentication_flutter_ddd
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── build.gradle.kts
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── settings.gradle.kts
├── guides
├── architecture.md
└── setup.md
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── RunnerTests
│ └── RunnerTests.swift
├── lib
├── app.dart
├── application
│ └── authentication
│ │ ├── auth_events.dart
│ │ ├── auth_events.freezed.dart
│ │ ├── auth_state_controller.dart
│ │ ├── auth_states.dart
│ │ └── auth_states.freezed.dart
├── core
│ └── theme
│ │ ├── animated_widgets.dart
│ │ └── app_theme.dart
├── domain
│ ├── authentication
│ │ ├── auth_failures.dart
│ │ ├── auth_failures.freezed.dart
│ │ ├── auth_value_failures.dart
│ │ ├── auth_value_failures.freezed.dart
│ │ ├── auth_value_objects.dart
│ │ ├── auth_value_validators.dart
│ │ └── i_auth_facade.dart
│ └── core
│ │ ├── errors.dart
│ │ └── value_object.dart
├── firebase_options.dart
├── main.dart
├── screens
│ ├── home_page.dart
│ ├── login_page.dart
│ ├── registration_page.dart
│ └── utils
│ │ └── custom_snackbar.dart
└── services
│ └── authentication
│ └── firebase_auth_facade.dart
├── opencode.json
├── pubspec.lock
├── pubspec.yaml
├── scripts
└── delete
└── web
├── favicon.png
├── icons
├── Icon-192.png
├── Icon-512.png
├── Icon-maskable-192.png
└── Icon-maskable-512.png
├── index.html
└── manifest.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | tab_width = 4
6 | indent_size = 4
7 | end_of_line = lf
8 | max_line_length = 120
9 | ij_visual_guides = 120
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.{js,py,html}]
14 | charset = utf-8
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | OLLAMA_API_KEY="ollama"
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pub" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 | /android/app/google-services.json
48 |
49 | .idea
50 | .dart_tool
51 |
52 | ios/firebase_app_id_file.json
53 |
54 | # Logs
55 | logs
56 | npm-debug.log*
57 | yarn-debug.log*
58 | yarn-error.log*
59 | dev-debug.log
60 | # Dependency directories
61 | node_modules/
62 | # Environment variables
63 | .env
64 | # Editor directories and files
65 | .vscode
66 | *.suo
67 | *.ntvs*
68 | *.njsproj
69 | *.sln
70 | *.sw?
71 | # OS specific
72 |
73 | # Task files
74 | # tasks.json
75 | # tasks/
76 |
77 | .gemini
--------------------------------------------------------------------------------
/.kiro/settings/mcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "task-master-ai": {
4 | "command": "npx",
5 | "args": ["-y", "--package=task-master-ai", "task-master-ai"],
6 | "env": {
7 | "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
8 | "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
9 | "OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
10 | "GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE",
11 | "XAI_API_KEY": "YOUR_XAI_KEY_HERE",
12 | "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
13 | "MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
14 | "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE",
15 | "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE"
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.kiro/steering/kiro_rules.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Guidelines for creating and maintaining Kiro rules to ensure consistency and effectiveness.
3 | globs: .kiro/steering/*.md
4 | alwaysApply: true
5 | ---
6 |
7 | - **Required Rule Structure:**
8 | ```markdown
9 | ---
10 | description: Clear, one-line description of what the rule enforces
11 | globs: path/to/files/*.ext, other/path/**/*
12 | alwaysApply: boolean
13 | ---
14 |
15 | - **Main Points in Bold**
16 | - Sub-points with details
17 | - Examples and explanations
18 | ```
19 |
20 | - **File References:**
21 | - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files
22 | - Example: [prisma.md](.kiro/steering/prisma.md) for rule references
23 | - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references
24 |
25 | - **Code Examples:**
26 | - Use language-specific code blocks
27 | ```typescript
28 | // ✅ DO: Show good examples
29 | const goodExample = true;
30 |
31 | // ❌ DON'T: Show anti-patterns
32 | const badExample = false;
33 | ```
34 |
35 | - **Rule Content Guidelines:**
36 | - Start with high-level overview
37 | - Include specific, actionable requirements
38 | - Show examples of correct implementation
39 | - Reference existing code when possible
40 | - Keep rules DRY by referencing other rules
41 |
42 | - **Rule Maintenance:**
43 | - Update rules when new patterns emerge
44 | - Add examples from actual codebase
45 | - Remove outdated patterns
46 | - Cross-reference related rules
47 |
48 | - **Best Practices:**
49 | - Use bullet points for clarity
50 | - Keep descriptions concise
51 | - Include both DO and DON'T examples
52 | - Reference actual code over theoretical examples
53 | - Use consistent formatting across rules
--------------------------------------------------------------------------------
/.kiro/steering/self_improve.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Guidelines for continuously improving Kiro rules based on emerging code patterns and best practices.
3 | globs: **/*
4 | alwaysApply: true
5 | ---
6 |
7 | - **Rule Improvement Triggers:**
8 | - New code patterns not covered by existing rules
9 | - Repeated similar implementations across files
10 | - Common error patterns that could be prevented
11 | - New libraries or tools being used consistently
12 | - Emerging best practices in the codebase
13 |
14 | - **Analysis Process:**
15 | - Compare new code with existing rules
16 | - Identify patterns that should be standardized
17 | - Look for references to external documentation
18 | - Check for consistent error handling patterns
19 | - Monitor test patterns and coverage
20 |
21 | - **Rule Updates:**
22 | - **Add New Rules When:**
23 | - A new technology/pattern is used in 3+ files
24 | - Common bugs could be prevented by a rule
25 | - Code reviews repeatedly mention the same feedback
26 | - New security or performance patterns emerge
27 |
28 | - **Modify Existing Rules When:**
29 | - Better examples exist in the codebase
30 | - Additional edge cases are discovered
31 | - Related rules have been updated
32 | - Implementation details have changed
33 |
34 | - **Example Pattern Recognition:**
35 | ```typescript
36 | // If you see repeated patterns like:
37 | const data = await prisma.user.findMany({
38 | select: { id: true, email: true },
39 | where: { status: 'ACTIVE' }
40 | });
41 |
42 | // Consider adding to [prisma.md](.kiro/steering/prisma.md):
43 | // - Standard select fields
44 | // - Common where conditions
45 | // - Performance optimization patterns
46 | ```
47 |
48 | - **Rule Quality Checks:**
49 | - Rules should be actionable and specific
50 | - Examples should come from actual code
51 | - References should be up to date
52 | - Patterns should be consistently enforced
53 |
54 | - **Continuous Improvement:**
55 | - Monitor code review comments
56 | - Track common development questions
57 | - Update rules after major refactors
58 | - Add links to relevant documentation
59 | - Cross-reference related rules
60 |
61 | - **Rule Deprecation:**
62 | - Mark outdated patterns as deprecated
63 | - Remove rules that no longer apply
64 | - Update references to deprecated rules
65 | - Document migration paths for old patterns
66 |
67 | - **Documentation Updates:**
68 | - Keep examples synchronized with code
69 | - Update references to external docs
70 | - Maintain links between related rules
71 | - Document breaking changes
72 | Follow [kiro_rules.md](.kiro/steering/kiro_rules.md) for proper rule formatting and structure.
73 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "077b4a4ce10a07b82caa6897f0c626f9c0a3ac90"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
17 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
18 | - platform: android
19 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
20 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
21 | - platform: ios
22 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
23 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
24 | - platform: linux
25 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
26 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
27 | - platform: macos
28 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
29 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
30 | - platform: web
31 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
32 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
33 | - platform: windows
34 | create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
35 | base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/.taskmaster/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "models": {
3 | "main": {
4 | "provider": "ollama",
5 | "modelId": "devstral:24b",
6 | "maxTokens": 64000,
7 | "temperature": 0.2
8 | },
9 | "research": {
10 | "provider": "ollama",
11 | "modelId": "gemma3:12b",
12 | "maxTokens": 8700,
13 | "temperature": 0.1
14 | },
15 | "fallback": {
16 | "provider": "ollama",
17 | "modelId": "phi4-mini:latest",
18 | "maxTokens": 8192,
19 | "temperature": 0.2
20 | }
21 | },
22 | "global": {
23 | "logLevel": "info",
24 | "debug": false,
25 | "defaultNumTasks": 10,
26 | "defaultSubtasks": 5,
27 | "defaultPriority": "medium",
28 | "projectName": "Task Master",
29 | "ollamaBaseURL": "http://127.0.0.1:11434/api",
30 | "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
31 | "responseLanguage": "English",
32 | "userId": "1234567890",
33 | "defaultTag": "master"
34 | },
35 | "claudeCode": {}
36 | }
37 |
--------------------------------------------------------------------------------
/.taskmaster/docs/prd.txt:
--------------------------------------------------------------------------------
1 |
2 | # Product Requirements Document: Firebase Authentication Flutter DDD App
3 |
4 | ## 1. Overview
5 |
6 | This document outlines the requirements for updating and improving the Firebase Authentication Flutter DDD application. The primary goals are to migrate to the latest stable version of Riverpod, modernize the user interface, and improve the overall quality and maintainability of the codebase.
7 |
8 | ## 2. Key Features and Improvements
9 |
10 | ### 2.1. Riverpod 3.0 Migration
11 |
12 | The application currently uses a pre-release version of Riverpod 3.0. This should be updated to the latest stable release of Riverpod 3.0. This migration will involve:
13 |
14 | - Updating the `hooks_riverpod` dependency in `pubspec.yaml`.
15 | - Refactoring the existing providers and widgets to align with the latest Riverpod 3.0 APIs.
16 | - Ensuring that the state management is robust and efficient.
17 |
18 | ### 2.2. UI Modernization
19 |
20 | The current user interface is basic and can be significantly improved. The new UI should be:
21 |
22 | - **Modern and visually appealing:** Adopt a clean and modern design language (e.g., Material You).
23 | - **User-friendly:** The user experience should be intuitive and seamless.
24 | - **Responsive:** The UI should adapt to different screen sizes and orientations.
25 |
26 | This will involve:
27 |
28 | - Redesigning the login, registration, and home pages.
29 | - Creating a consistent theme and style guide.
30 | - Adding animations and transitions to enhance the user experience.
31 |
32 | ### 2.3. Code Refactoring and Cleanup
33 |
34 | The codebase should be refactored to improve its quality, readability, and maintainability. This includes:
35 |
36 | - **Updating dependencies:** All dependencies should be updated to their latest stable versions.
37 | - **Improving error handling:** Implement a more robust error handling mechanism to gracefully handle exceptions and provide clear feedback to the user.
38 | - **Enhancing validation:** Strengthen the input validation for the login and registration forms.
39 | - **Code organization:** Ensure that the code is well-organized and follows the principles of Domain-Driven Design (DDD).
40 |
41 | ### 2.4. Enhanced Authentication Flow
42 |
43 | The authentication flow can be improved by adding the following features:
44 |
45 | - **Password reset:** Allow users to reset their passwords if they forget them.
46 | - **Email verification:** Send a verification email to new users to ensure that they have provided a valid email address.
47 | - **Social logins:** Add support for social logins (e.g., Google, Apple) to provide users with more options for signing in.
48 |
49 | ## 3. Non-Functional Requirements
50 |
51 | - **Performance:** The application should be performant and responsive, with minimal jank or lag.
52 | - **Security:** The application should be secure and protect user data.
53 | - **Scalability:** The architecture should be scalable to accommodate future growth and new features.
54 |
55 | ## 4. Future Enhancements
56 |
57 | - **Profile page:** A user profile page where users can view and edit their information.
58 | - **Two-factor authentication:** Add an extra layer of security with two-factor authentication.
59 | - **Offline support:** Cache data locally to provide a seamless experience even when the user is offline.
60 |
--------------------------------------------------------------------------------
/.taskmaster/state.json:
--------------------------------------------------------------------------------
1 | {
2 | "currentTag": "master",
3 | "lastSwitched": "2025-07-21T07:37:41.540Z",
4 | "branchTagMapping": {},
5 | "migrationNoticeShown": true
6 | }
--------------------------------------------------------------------------------
/.taskmaster/tasks/task_001.txt:
--------------------------------------------------------------------------------
1 | # Task ID: 1
2 | # Title: Migrate to Riverpod 3.0
3 | # Status: done
4 | # Dependencies: None
5 | # Priority: high
6 | # Description: Update the application to use the latest stable version of Riverpod 3.0. This includes updating the `hooks_riverpod` dependency, refactoring providers and widgets, and ensuring the state management is robust.
7 | # Details:
8 |
9 |
10 | # Test Strategy:
11 |
12 |
--------------------------------------------------------------------------------
/.taskmaster/tasks/task_002.txt:
--------------------------------------------------------------------------------
1 | # Task ID: 2
2 | # Title: Modernize the User Interface
3 | # Status: done
4 | # Dependencies: 1
5 | # Priority: high
6 | # Description: Redesign the login, registration, and home pages to be more modern and visually appealing. Adopt a clean design language like Material You, create a consistent theme, and add animations to enhance the user experience.
7 | # Details:
8 |
9 |
10 | # Test Strategy:
11 |
12 |
--------------------------------------------------------------------------------
/.taskmaster/tasks/task_003.txt:
--------------------------------------------------------------------------------
1 | # Task ID: 3
2 | # Title: Refactor and Clean Up Codebase
3 | # Status: pending
4 | # Dependencies: 1
5 | # Priority: medium
6 | # Description: Improve the quality, readability, and maintainability of the codebase. This includes updating all dependencies to their latest stable versions, improving error handling, enhancing input validation, and ensuring the code is well-organized.
7 | # Details:
8 |
9 |
10 | # Test Strategy:
11 |
12 |
--------------------------------------------------------------------------------
/.taskmaster/tasks/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "master": {
3 | "tasks": [
4 | {
5 | "id": 1,
6 | "title": "Migrate to Riverpod 3.0",
7 | "description": "Update the application to use the latest stable version of Riverpod 3.0. This includes updating the `hooks_riverpod` dependency, refactoring providers and widgets, and ensuring the state management is robust.",
8 | "status": "done",
9 | "priority": "high",
10 | "dependencies": []
11 | },
12 | {
13 | "id": 2,
14 | "title": "Modernize the User Interface",
15 | "description": "Redesign the login, registration, and home pages to be more modern and visually appealing. Adopt a clean design language like Material You, create a consistent theme, and add animations to enhance the user experience.",
16 | "status": "done",
17 | "priority": "high",
18 | "dependencies": [
19 | 1
20 | ]
21 | },
22 | {
23 | "id": 3,
24 | "title": "Refactor and Clean Up Codebase",
25 | "description": "Improve the quality, readability, and maintainability of the codebase. This includes updating all dependencies to their latest stable versions, improving error handling, enhancing input validation, and ensuring the code is well-organized.",
26 | "status": "pending",
27 | "dependencies": [
28 | 1
29 | ],
30 | "priority": "medium",
31 | "details": "",
32 | "testStrategy": "",
33 | "subtasks": [
34 | {
35 | "id": 1,
36 | "title": "Refactor Domain Layer",
37 | "description": "Fix typos, enhance email validation with industry-standard regex, strengthen password security (8+ chars)",
38 | "status": "done",
39 | "dependencies": [],
40 | "details": "",
41 | "testStrategy": ""
42 | },
43 | {
44 | "id": 2,
45 | "title": "Refactor Application Layer",
46 | "description": "Streamline AuthStateController, improve error handling, cleaner code structure",
47 | "status": "done",
48 | "dependencies": [],
49 | "details": "",
50 | "testStrategy": ""
51 | },
52 | {
53 | "id": 3,
54 | "title": "Refactor Services Layer",
55 | "description": "Enhance Firebase Auth Facade with centralized error mapping and better null safety",
56 | "status": "done",
57 | "dependencies": [],
58 | "details": "",
59 | "testStrategy": ""
60 | },
61 | {
62 | "id": 4,
63 | "title": "Refactor UI Components",
64 | "description": "Refactor Login/Registration pages with modular structure, extract helper methods, enhance validation",
65 | "status": "done",
66 | "dependencies": [],
67 | "details": "",
68 | "testStrategy": ""
69 | },
70 | {
71 | "id": 5,
72 | "title": "App Level Improvements",
73 | "description": "Simplify routing, add global page transitions, performance improvements",
74 | "status": "done",
75 | "dependencies": [],
76 | "details": "",
77 | "testStrategy": ""
78 | },
79 | {
80 | "id": 6,
81 | "title": "Update Dependencies",
82 | "description": "Update all dependencies to their latest stable versions",
83 | "status": "pending",
84 | "dependencies": [],
85 | "details": "",
86 | "testStrategy": ""
87 | }
88 | ]
89 | },
90 | {
91 | "id": 4,
92 | "title": "Implement Password Reset",
93 | "description": "Allow users to reset their passwords if they forget them. This involves creating a 'Forgot Password' screen and integrating with Firebase Authentication's password reset functionality.",
94 | "status": "pending",
95 | "priority": "medium",
96 | "dependencies": [
97 | 1
98 | ]
99 | },
100 | {
101 | "id": 5,
102 | "title": "Implement Email Verification",
103 | "description": "Send a verification email to new users to ensure that they have provided a valid email address. This will help to reduce spam and ensure that users can be reached.",
104 | "status": "pending",
105 | "priority": "medium",
106 | "dependencies": [
107 | 1
108 | ]
109 | },
110 | {
111 | "id": 6,
112 | "title": "Add Social Logins",
113 | "description": "Add support for social logins (e.g., Google, Apple) to provide users with more options for signing in. This will improve the user experience and make it easier for users to get started with the app.",
114 | "status": "pending",
115 | "priority": "low",
116 | "dependencies": [
117 | 1
118 | ]
119 | },
120 | {
121 | "id": 7,
122 | "title": "Create User Profile Page",
123 | "description": "Create a user profile page where users can view and edit their information. This will give users more control over their data and allow them to personalize their experience.",
124 | "status": "pending",
125 | "priority": "low",
126 | "dependencies": [
127 | 1
128 | ]
129 | },
130 | {
131 | "id": 8,
132 | "title": "Implement Two-Factor Authentication",
133 | "description": "Add an extra layer of security with two-factor authentication. This will help to protect user accounts from unauthorized access.",
134 | "status": "pending",
135 | "priority": "low",
136 | "dependencies": [
137 | 1
138 | ]
139 | },
140 | {
141 | "id": 9,
142 | "title": "Implement Offline Support",
143 | "description": "Cache data locally to provide a seamless experience even when the user is offline. This will improve the user experience and make the app more resilient to network interruptions.",
144 | "status": "pending",
145 | "priority": "low",
146 | "dependencies": [
147 | 1
148 | ]
149 | }
150 | ],
151 | "metadata": {
152 | "created": "2025-07-21T07:47:36.467Z",
153 | "updated": "2025-07-21T13:00:48.148Z",
154 | "description": "Tasks for master context"
155 | }
156 | }
157 | }
--------------------------------------------------------------------------------
/.taskmaster/templates/example_prd.txt:
--------------------------------------------------------------------------------
1 |
2 | # Overview
3 | [Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]
4 |
5 | # Core Features
6 | [List and describe the main features of your product. For each feature, include:
7 | - What it does
8 | - Why it's important
9 | - How it works at a high level]
10 |
11 | # User Experience
12 | [Describe the user journey and experience. Include:
13 | - User personas
14 | - Key user flows
15 | - UI/UX considerations]
16 |
17 |
18 | # Technical Architecture
19 | [Outline the technical implementation details:
20 | - System components
21 | - Data models
22 | - APIs and integrations
23 | - Infrastructure requirements]
24 |
25 | # Development Roadmap
26 | [Break down the development process into phases:
27 | - MVP requirements
28 | - Future enhancements
29 | - Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]
30 |
31 | # Logical Dependency Chain
32 | [Define the logical order of development:
33 | - Which features need to be built first (foundation)
34 | - Getting as quickly as possible to something usable/visible front end that works
35 | - Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]
36 |
37 | # Risks and Mitigations
38 | [Identify potential risks and how they'll be addressed:
39 | - Technical challenges
40 | - Figuring out the MVP that we can build upon
41 | - Resource constraints]
42 |
43 | # Appendix
44 | [Include any additional information:
45 | - Research findings
46 | - Technical specifications]
47 |
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
1 | # Task Master AI - Agent Integration Guide
2 |
3 | ## Essential Commands
4 |
5 | ### Core Workflow Commands
6 |
7 | ```bash
8 | # Project Setup
9 | task-master init # Initialize Task Master in current project
10 | task-master parse-prd .taskmaster/docs/prd.txt # Generate tasks from PRD document
11 | task-master models --setup # Configure AI models interactively
12 |
13 | # Daily Development Workflow
14 | task-master list # Show all tasks with status
15 | task-master next # Get next available task to work on
16 | task-master show # View detailed task information (e.g., task-master show 1.2)
17 | task-master set-status --id= --status=done # Mark task complete
18 |
19 | # Task Management
20 | task-master add-task --prompt="description" --research # Add new task with AI assistance
21 | task-master expand --id= --research --force # Break task into subtasks
22 | task-master update-task --id= --prompt="changes" # Update specific task
23 | task-master update --from= --prompt="changes" # Update multiple tasks from ID onwards
24 | task-master update-subtask --id= --prompt="notes" # Add implementation notes to subtask
25 |
26 | # Analysis & Planning
27 | task-master analyze-complexity --research # Analyze task complexity
28 | task-master complexity-report # View complexity analysis
29 | task-master expand --all --research # Expand all eligible tasks
30 |
31 | # Dependencies & Organization
32 | task-master add-dependency --id= --depends-on= # Add task dependency
33 | task-master move --from= --to= # Reorganize task hierarchy
34 | task-master validate-dependencies # Check for dependency issues
35 | task-master generate # Update task markdown files (usually auto-called)
36 | ```
37 |
38 | ## Key Files & Project Structure
39 |
40 | ### Core Files
41 |
42 | - `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed)
43 | - `.taskmaster/config.json` - AI model configuration (use `task-master models`to
44 | modify)
45 | - `.taskmaster/docs/prd.txt` - Product Requirements Document for parsing
46 | - `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from
47 | tasks.json)
48 | - `.env` - API keys for CLI usage
49 |
50 | ### Claude Code Integration Files
51 |
52 | - `CLAUDE.md` - Auto-loaded context for Claude Code (this file)
53 | - `.claude/settings.json` - Claude Code tool allowlist and preferences
54 | - `.claude/commands/` - Custom slash commands for repeated workflows
55 | - `.mcp.json` - MCP server configuration (project-specific)
56 |
57 | ### Directory Structure
58 |
59 | ```
60 | project/
61 | ├── .taskmaster/
62 | │ ├── tasks/ # Task files directory
63 | │ │ ├── tasks.json # Main task database
64 | │ │ ├── task-1.md # Individual task files
65 | │ │ └── task-2.md
66 | │ ├── docs/ # Documentation directory
67 | │ │ ├── prd.txt # Product requirements
68 | │ ├── reports/ # Analysis reports directory
69 | │ │ └── task-complexity-report.json
70 | │ ├── templates/ # Template files
71 | │ │ └── example_prd.txt # Example PRD template
72 | │ └── config.json # AI models & settings
73 | ├── .claude/
74 | │ ├── settings.json # Claude Code configuration
75 | │ └── commands/ # Custom slash commands
76 | ├── .env # API keys
77 | ├── .mcp.json # MCP configuration
78 | └── CLAUDE.md # This file - auto-loaded by Claude Code
79 | ```
80 |
81 | ## MCP Integration
82 |
83 | Task Master provides an MCP server that Claude Code can connect to. Configure in
84 | `.mcp.json`:
85 |
86 | ```json
87 | {
88 | "mcpServers": {
89 | "task-master-ai": {
90 | "command": "npx",
91 | "args": [
92 | "-y",
93 | "--package=task-master-ai",
94 | "task-master-ai"
95 | ],
96 | "env": {
97 | "ANTHROPIC_API_KEY": "your_key_here",
98 | "PERPLEXITY_API_KEY": "your_key_here",
99 | "OPENAI_API_KEY": "OPENAI_API_KEY_HERE",
100 | "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE",
101 | "XAI_API_KEY": "XAI_API_KEY_HERE",
102 | "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE",
103 | "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE",
104 | "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE",
105 | "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE"
106 | }
107 | }
108 | }
109 | }
110 | ```
111 |
112 | ### Essential MCP Tools
113 |
114 | ```javascript
115 | help; // = shows available taskmaster commands
116 | // Project setup
117 | initialize_project; // = task-master init
118 | parse_prd; // = task-master parse-prd
119 |
120 | // Daily workflow
121 | get_tasks; // = task-master list
122 | next_task; // = task-master next
123 | get_task; // = task-master show
124 | set_task_status; // = task-master set-status
125 |
126 | // Task management
127 | add_task; // = task-master add-task
128 | expand_task; // = task-master expand
129 | update_task; // = task-master update-task
130 | update_subtask; // = task-master update-subtask
131 | update; // = task-master update
132 |
133 | // Analysis
134 | analyze_project_complexity; // = task-master analyze-complexity
135 | complexity_report; // = task-master complexity-report
136 | ```
137 |
138 | ## Claude Code Workflow Integration
139 |
140 | ### Standard Development Workflow
141 |
142 | #### 1. Project Initialization
143 |
144 | ```bash
145 | # Initialize Task Master
146 | task-master init
147 |
148 | # Create or obtain PRD, then parse it
149 | task-master parse-prd .taskmaster/docs/prd.txt
150 |
151 | # Analyze complexity and expand tasks
152 | task-master analyze-complexity --research
153 | task-master expand --all --research
154 | ```
155 |
156 | If tasks already exist, another PRD can be parsed (with new information only!)
157 | using parse-prd with --append flag. This will add the generated tasks to the
158 | existing list of tasks..
159 |
160 | #### 2. Daily Development Loop
161 |
162 | ```bash
163 | # Start each session
164 | task-master next # Find next available task
165 | task-master show # Review task details
166 |
167 | # During implementation, check in code context into the tasks and subtasks
168 | task-master update-subtask --id= --prompt="implementation notes..."
169 |
170 | # Complete tasks
171 | task-master set-status --id= --status=done
172 | ```
173 |
174 | #### 3. Multi-Claude Workflows
175 |
176 | For complex projects, use multiple Claude Code sessions:
177 |
178 | ```bash
179 | # Terminal 1: Main implementation
180 | cd project && claude
181 |
182 | # Terminal 2: Testing and validation
183 | cd project-test-worktree && claude
184 |
185 | # Terminal 3: Documentation updates
186 | cd project-docs-worktree && claude
187 | ```
188 |
189 | ### Custom Slash Commands
190 |
191 | Create `.claude/commands/taskmaster-next.md`:
192 |
193 | ```markdown
194 | Find the next available Task Master task and show its details.
195 |
196 | Steps:
197 |
198 | 1. Run `task-master next` to get the next task
199 | 2. If a task is available, run `task-master show ` for full details
200 | 3. Provide a summary of what needs to be implemented
201 | 4. Suggest the first implementation step
202 | ```
203 |
204 | Create `.claude/commands/taskmaster-complete.md`:
205 |
206 | ```markdown
207 | Complete a Task Master task: $ARGUMENTS
208 |
209 | Steps:
210 |
211 | 1. Review the current task with `task-master show $ARGUMENTS`
212 | 2. Verify all implementation is complete
213 | 3. Run any tests related to this task
214 | 4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done`
215 | 5. Show the next available task with `task-master next`
216 | ```
217 |
218 | ## Tool Allowlist Recommendations
219 |
220 | Add to `.claude/settings.json`:
221 |
222 | ```json
223 | {
224 | "allowedTools": [
225 | "Edit",
226 | "Bash(task-master *)",
227 | "Bash(git commit:*)",
228 | "Bash(git add:*)",
229 | "Bash(npm run *)",
230 | "mcp__task_master_ai__*"
231 | ]
232 | }
233 | ```
234 |
235 | ## Configuration & Setup
236 |
237 | ### API Keys Required
238 |
239 | At least **one** of these API keys must be configured:
240 |
241 | - `ANTHROPIC_API_KEY` (Claude models) - **Recommended**
242 | - `PERPLEXITY_API_KEY` (Research features) - **Highly recommended**
243 | - `OPENAI_API_KEY` (GPT models)
244 | - `GOOGLE_API_KEY` (Gemini models)
245 | - `MISTRAL_API_KEY` (Mistral models)
246 | - `OPENROUTER_API_KEY` (Multiple models)
247 | - `XAI_API_KEY` (Grok models)
248 |
249 | An API key is required for any provider used across any of the 3 roles defined
250 | in the `models` command.
251 |
252 | ### Model Configuration
253 |
254 | ```bash
255 | # Interactive setup (recommended)
256 | task-master models --setup
257 |
258 | # Set specific models
259 | task-master models --set-main claude-3-5-sonnet-20241022
260 | task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
261 | task-master models --set-fallback gpt-4o-mini
262 | ```
263 |
264 | ## Task Structure & IDs
265 |
266 | ### Task ID Format
267 |
268 | - Main tasks: `1`, `2`, `3`, etc.
269 | - Subtasks: `1.1`, `1.2`, `2.1`, etc.
270 | - Sub-subtasks: `1.1.1`, `1.1.2`, etc.
271 |
272 | ### Task Status Values
273 |
274 | - `pending` - Ready to work on
275 | - `in-progress` - Currently being worked on
276 | - `done` - Completed and verified
277 | - `deferred` - Postponed
278 | - `cancelled` - No longer needed
279 | - `blocked` - Waiting on external factors
280 |
281 | ### Task Fields
282 |
283 | ```json
284 | {
285 | "id": "1.2",
286 | "title": "Implement user authentication",
287 | "description": "Set up JWT-based auth system",
288 | "status": "pending",
289 | "priority": "high",
290 | "dependencies": [
291 | "1.1"
292 | ],
293 | "details": "Use bcrypt for hashing, JWT for tokens...",
294 | "testStrategy": "Unit tests for auth functions, integration tests for login flow",
295 | "subtasks": []
296 | }
297 | ```
298 |
299 | ## Claude Code Best Practices with Task Master
300 |
301 | ### Context Management
302 |
303 | - Use `/clear` between different tasks to maintain focus
304 | - This CLAUDE.md file is automatically loaded for context
305 | - Use `task-master show ` to pull specific task context when needed
306 |
307 | ### Iterative Implementation
308 |
309 | 1. `task-master show ` - Understand requirements
310 | 2. Explore codebase and plan implementation
311 | 3. `task-master update-subtask --id= --prompt="detailed plan"` - Log plan
312 | 4. `task-master set-status --id= --status=in-progress` - Start work
313 | 5. Implement code following logged plan
314 | 6. `task-master update-subtask --id= --prompt="what worked/didn't work"` -
315 | Log progress
316 | 7. `task-master set-status --id= --status=done` - Complete task
317 |
318 | ### Complex Workflows with Checklists
319 |
320 | For large migrations or multi-step processes:
321 |
322 | 1. Create a markdown PRD file describing the new changes:
323 | `touch task-migration-checklist.md` (prds can be .txt or .md)
324 | 2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` (
325 | also available in MCP)
326 | 3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier
327 | using `analyze-complexity` with the correct --to and --from IDs (the new ids)
328 | to identify the ideal subtask amounts for each task. Then expand them.
329 | 4. Work through items systematically, checking them off as completed
330 | 5. Use `task-master update-subtask` to log progress on each task/subtask and/or
331 | updating/researching them before/during implementation if getting stuck
332 |
333 | ### Git Integration
334 |
335 | Task Master works well with `gh` CLI:
336 |
337 | ```bash
338 | # Create PR for completed task
339 | gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2"
340 |
341 | # Reference task in commits
342 | git commit -m "feat: implement JWT auth (task 1.2)"
343 | ```
344 |
345 | ### Parallel Development with Git Worktrees
346 |
347 | ```bash
348 | # Create worktrees for parallel task development
349 | git worktree add ../project-auth feature/auth-system
350 | git worktree add ../project-api feature/api-refactor
351 |
352 | # Run Claude Code in each worktree
353 | cd ../project-auth && claude # Terminal 1: Auth work
354 | cd ../project-api && claude # Terminal 2: API work
355 | ```
356 |
357 | ## Troubleshooting
358 |
359 | ### AI Commands Failing
360 |
361 | ```bash
362 | # Check API keys are configured
363 | cat .env # For CLI usage
364 |
365 | # Verify model configuration
366 | task-master models
367 |
368 | # Test with different model
369 | task-master models --set-fallback gpt-4o-mini
370 | ```
371 |
372 | ### MCP Connection Issues
373 |
374 | - Check `.mcp.json` configuration
375 | - Verify Node.js installation
376 | - Use `--mcp-debug` flag when starting Claude Code
377 | - Use CLI as fallback if MCP unavailable
378 |
379 | ### Task File Sync Issues
380 |
381 | ```bash
382 | # Regenerate task files from tasks.json
383 | task-master generate
384 |
385 | # Fix dependency issues
386 | task-master fix-dependencies
387 | ```
388 |
389 | DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same
390 | Taskmaster core files.
391 |
392 | ## Important Notes
393 |
394 | ### AI-Powered Operations
395 |
396 | These commands make AI calls and may take up to a minute:
397 |
398 | - `parse_prd` / `task-master parse-prd`
399 | - `analyze_project_complexity` / `task-master analyze-complexity`
400 | - `expand_task` / `task-master expand`
401 | - `expand_all` / `task-master expand --all`
402 | - `add_task` / `task-master add-task`
403 | - `update` / `task-master update`
404 | - `update_task` / `task-master update-task`
405 | - `update_subtask` / `task-master update-subtask`
406 |
407 | ### File Management
408 |
409 | - Never manually edit `tasks.json` - use commands instead
410 | - Never manually edit `.taskmaster/config.json` - use `task-master models`
411 | - Task markdown files in `tasks/` are auto-generated
412 | - Run `task-master generate` after manual changes to tasks.json
413 |
414 | ### Claude Code Session Management
415 |
416 | - Use `/clear` frequently to maintain focused context
417 | - Create custom slash commands for repeated Task Master workflows
418 | - Configure tool allowlist to streamline permissions
419 | - Use headless mode for automation: `claude -p "task-master next"`
420 |
421 | ### Multi-Task Updates
422 |
423 | - Use `update --from=` to update multiple future tasks
424 | - Use `update-task --id=` for single task updates
425 | - Use `update-subtask --id=` for implementation logging
426 |
427 | ### Research Mode
428 |
429 | - Add `--research` flag for research-based AI enhancement
430 | - Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in
431 | environment
432 | - Provides more informed task creation and updates
433 | - Recommended for complex technical tasks
434 |
435 | ---
436 |
437 | _This guide ensures Claude Code has immediate access to Task Master's essential
438 | functionality for agentic development workflows._
439 |
--------------------------------------------------------------------------------
/GEMINI.md:
--------------------------------------------------------------------------------
1 | # Task Master AI - Agent Integration Guide
2 |
3 | ## Essential Commands
4 |
5 | ### Core Workflow Commands
6 |
7 | ```bash
8 | # Project Setup
9 | task-master init # Initialize Task Master in current project
10 | task-master parse-prd .taskmaster/docs/prd.txt # Generate tasks from PRD document
11 | task-master models --setup # Configure AI models interactively
12 |
13 | # Daily Development Workflow
14 | task-master list # Show all tasks with status
15 | task-master next # Get next available task to work on
16 | task-master show # View detailed task information (e.g., task-master show 1.2)
17 | task-master set-status --id= --status=done # Mark task complete
18 |
19 | # Task Management
20 | task-master add-task --prompt="description" --research # Add new task with AI assistance
21 | task-master expand --id= --research --force # Break task into subtasks
22 | task-master update-task --id= --prompt="changes" # Update specific task
23 | task-master update --from= --prompt="changes" # Update multiple tasks from ID onwards
24 | task-master update-subtask --id= --prompt="notes" # Add implementation notes to subtask
25 |
26 | # Analysis & Planning
27 | task-master analyze-complexity --research # Analyze task complexity
28 | task-master complexity-report # View complexity analysis
29 | task-master expand --all --research # Expand all eligible tasks
30 |
31 | # Dependencies & Organization
32 | task-master add-dependency --id= --depends-on= # Add task dependency
33 | task-master move --from= --to= # Reorganize task hierarchy
34 | task-master validate-dependencies # Check for dependency issues
35 | task-master generate # Update task markdown files (usually auto-called)
36 | ```
37 |
38 | ## Key Files & Project Structure
39 |
40 | ### Core Files
41 |
42 | - `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed)
43 | - `.taskmaster/config.json` - AI model configuration (use `task-master models`to
44 | modify)
45 | - `.taskmaster/docs/prd.txt` - Product Requirements Document for parsing
46 | - `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from
47 | tasks.json)
48 | - `.env` - API keys for CLI usage
49 |
50 | ### Claude Code Integration Files
51 |
52 | - `CLAUDE.md` - Auto-loaded context for Claude Code (this file)
53 | - `.claude/settings.json` - Claude Code tool allowlist and preferences
54 | - `.claude/commands/` - Custom slash commands for repeated workflows
55 | - `.mcp.json` - MCP server configuration (project-specific)
56 |
57 | ### Directory Structure
58 |
59 | ```
60 | project/
61 | ├── .taskmaster/
62 | │ ├── tasks/ # Task files directory
63 | │ │ ├── tasks.json # Main task database
64 | │ │ ├── task-1.md # Individual task files
65 | │ │ └── task-2.md
66 | │ ├── docs/ # Documentation directory
67 | │ │ ├── prd.txt # Product requirements
68 | │ ├── reports/ # Analysis reports directory
69 | │ │ └── task-complexity-report.json
70 | │ ├── templates/ # Template files
71 | │ │ └── example_prd.txt # Example PRD template
72 | │ └── config.json # AI models & settings
73 | ├── .claude/
74 | │ ├── settings.json # Claude Code configuration
75 | │ └── commands/ # Custom slash commands
76 | ├── .env # API keys
77 | ├── .mcp.json # MCP configuration
78 | └── CLAUDE.md # This file - auto-loaded by Claude Code
79 | ```
80 |
81 | ## MCP Integration
82 |
83 | Task Master provides an MCP server that Claude Code can connect to. Configure in
84 | `.mcp.json`:
85 |
86 | ```json
87 | {
88 | "mcpServers": {
89 | "task-master-ai": {
90 | "command": "npx",
91 | "args": [
92 | "-y",
93 | "--package=task-master-ai",
94 | "task-master-ai"
95 | ],
96 | "env": {
97 | "ANTHROPIC_API_KEY": "your_key_here",
98 | "PERPLEXITY_API_KEY": "your_key_here",
99 | "OPENAI_API_KEY": "OPENAI_API_KEY_HERE",
100 | "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE",
101 | "XAI_API_KEY": "XAI_API_KEY_HERE",
102 | "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE",
103 | "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE",
104 | "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE",
105 | "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE"
106 | }
107 | }
108 | }
109 | }
110 | ```
111 |
112 | ### Essential MCP Tools
113 |
114 | ```javascript
115 | help; // = shows available taskmaster commands
116 | // Project setup
117 | initialize_project; // = task-master init
118 | parse_prd; // = task-master parse-prd
119 |
120 | // Daily workflow
121 | get_tasks; // = task-master list
122 | next_task; // = task-master next
123 | get_task; // = task-master show
124 | set_task_status; // = task-master set-status
125 |
126 | // Task management
127 | add_task; // = task-master add-task
128 | expand_task; // = task-master expand
129 | update_task; // = task-master update-task
130 | update_subtask; // = task-master update-subtask
131 | update; // = task-master update
132 |
133 | // Analysis
134 | analyze_project_complexity; // = task-master analyze-complexity
135 | complexity_report; // = task-master complexity-report
136 | ```
137 |
138 | ## Claude Code Workflow Integration
139 |
140 | ### Standard Development Workflow
141 |
142 | #### 1. Project Initialization
143 |
144 | ```bash
145 | # Initialize Task Master
146 | task-master init
147 |
148 | # Create or obtain PRD, then parse it
149 | task-master parse-prd .taskmaster/docs/prd.txt
150 |
151 | # Analyze complexity and expand tasks
152 | task-master analyze-complexity --research
153 | task-master expand --all --research
154 | ```
155 |
156 | If tasks already exist, another PRD can be parsed (with new information only!)
157 | using parse-prd with --append flag. This will add the generated tasks to the
158 | existing list of tasks..
159 |
160 | #### 2. Daily Development Loop
161 |
162 | ```bash
163 | # Start each session
164 | task-master next # Find next available task
165 | task-master show # Review task details
166 |
167 | # During implementation, check in code context into the tasks and subtasks
168 | task-master update-subtask --id= --prompt="implementation notes..."
169 |
170 | # Complete tasks
171 | task-master set-status --id= --status=done
172 | ```
173 |
174 | #### 3. Multi-Claude Workflows
175 |
176 | For complex projects, use multiple Claude Code sessions:
177 |
178 | ```bash
179 | # Terminal 1: Main implementation
180 | cd project && claude
181 |
182 | # Terminal 2: Testing and validation
183 | cd project-test-worktree && claude
184 |
185 | # Terminal 3: Documentation updates
186 | cd project-docs-worktree && claude
187 | ```
188 |
189 | ### Custom Slash Commands
190 |
191 | Create `.claude/commands/taskmaster-next.md`:
192 |
193 | ```markdown
194 | Find the next available Task Master task and show its details.
195 |
196 | Steps:
197 |
198 | 1. Run `task-master next` to get the next task
199 | 2. If a task is available, run `task-master show ` for full details
200 | 3. Provide a summary of what needs to be implemented
201 | 4. Suggest the first implementation step
202 | ```
203 |
204 | Create `.claude/commands/taskmaster-complete.md`:
205 |
206 | ```markdown
207 | Complete a Task Master task: $ARGUMENTS
208 |
209 | Steps:
210 |
211 | 1. Review the current task with `task-master show $ARGUMENTS`
212 | 2. Verify all implementation is complete
213 | 3. Run any tests related to this task
214 | 4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done`
215 | 5. Show the next available task with `task-master next`
216 | ```
217 |
218 | ## Tool Allowlist Recommendations
219 |
220 | Add to `.claude/settings.json`:
221 |
222 | ```json
223 | {
224 | "allowedTools": [
225 | "Edit",
226 | "Bash(task-master *)",
227 | "Bash(git commit:*)",
228 | "Bash(git add:*)",
229 | "Bash(npm run *)",
230 | "mcp__task_master_ai__*"
231 | ]
232 | }
233 | ```
234 |
235 | ## Configuration & Setup
236 |
237 | ### API Keys Required
238 |
239 | At least **one** of these API keys must be configured:
240 |
241 | - `ANTHROPIC_API_KEY` (Claude models) - **Recommended**
242 | - `PERPLEXITY_API_KEY` (Research features) - **Highly recommended**
243 | - `OPENAI_API_KEY` (GPT models)
244 | - `GOOGLE_API_KEY` (Gemini models)
245 | - `MISTRAL_API_KEY` (Mistral models)
246 | - `OPENROUTER_API_KEY` (Multiple models)
247 | - `XAI_API_KEY` (Grok models)
248 |
249 | An API key is required for any provider used across any of the 3 roles defined
250 | in the `models` command.
251 |
252 | ### Model Configuration
253 |
254 | ```bash
255 | # Interactive setup (recommended)
256 | task-master models --setup
257 |
258 | # Set specific models
259 | task-master models --set-main claude-3-5-sonnet-20241022
260 | task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
261 | task-master models --set-fallback gpt-4o-mini
262 | ```
263 |
264 | ## Task Structure & IDs
265 |
266 | ### Task ID Format
267 |
268 | - Main tasks: `1`, `2`, `3`, etc.
269 | - Subtasks: `1.1`, `1.2`, `2.1`, etc.
270 | - Sub-subtasks: `1.1.1`, `1.1.2`, etc.
271 |
272 | ### Task Status Values
273 |
274 | - `pending` - Ready to work on
275 | - `in-progress` - Currently being worked on
276 | - `done` - Completed and verified
277 | - `deferred` - Postponed
278 | - `cancelled` - No longer needed
279 | - `blocked` - Waiting on external factors
280 |
281 | ### Task Fields
282 |
283 | ```json
284 | {
285 | "id": "1.2",
286 | "title": "Implement user authentication",
287 | "description": "Set up JWT-based auth system",
288 | "status": "pending",
289 | "priority": "high",
290 | "dependencies": [
291 | "1.1"
292 | ],
293 | "details": "Use bcrypt for hashing, JWT for tokens...",
294 | "testStrategy": "Unit tests for auth functions, integration tests for login flow",
295 | "subtasks": []
296 | }
297 | ```
298 |
299 | ## Claude Code Best Practices with Task Master
300 |
301 | ### Context Management
302 |
303 | - Use `/clear` between different tasks to maintain focus
304 | - This CLAUDE.md file is automatically loaded for context
305 | - Use `task-master show ` to pull specific task context when needed
306 |
307 | ### Iterative Implementation
308 |
309 | 1. `task-master show ` - Understand requirements
310 | 2. Explore codebase and plan implementation
311 | 3. `task-master update-subtask --id= --prompt="detailed plan"` - Log plan
312 | 4. `task-master set-status --id= --status=in-progress` - Start work
313 | 5. Implement code following logged plan
314 | 6. `task-master update-subtask --id= --prompt="what worked/didn't work"` -
315 | Log progress
316 | 7. `task-master set-status --id= --status=done` - Complete task
317 |
318 | ### Complex Workflows with Checklists
319 |
320 | For large migrations or multi-step processes:
321 |
322 | 1. Create a markdown PRD file describing the new changes:
323 | `touch task-migration-checklist.md` (prds can be .txt or .md)
324 | 2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` (
325 | also available in MCP)
326 | 3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier
327 | using `analyze-complexity` with the correct --to and --from IDs (the new ids)
328 | to identify the ideal subtask amounts for each task. Then expand them.
329 | 4. Work through items systematically, checking them off as completed
330 | 5. Use `task-master update-subtask` to log progress on each task/subtask and/or
331 | updating/researching them before/during implementation if getting stuck
332 |
333 | ### Git Integration
334 |
335 | Task Master works well with `gh` CLI:
336 |
337 | ```bash
338 | # Create PR for completed task
339 | gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2"
340 |
341 | # Reference task in commits
342 | git commit -m "feat: implement JWT auth (task 1.2)"
343 | ```
344 |
345 | ### Parallel Development with Git Worktrees
346 |
347 | ```bash
348 | # Create worktrees for parallel task development
349 | git worktree add ../project-auth feature/auth-system
350 | git worktree add ../project-api feature/api-refactor
351 |
352 | # Run Claude Code in each worktree
353 | cd ../project-auth && claude # Terminal 1: Auth work
354 | cd ../project-api && claude # Terminal 2: API work
355 | ```
356 |
357 | ## Troubleshooting
358 |
359 | ### AI Commands Failing
360 |
361 | ```bash
362 | # Check API keys are configured
363 | cat .env # For CLI usage
364 |
365 | # Verify model configuration
366 | task-master models
367 |
368 | # Test with different model
369 | task-master models --set-fallback gpt-4o-mini
370 | ```
371 |
372 | ### MCP Connection Issues
373 |
374 | - Check `.mcp.json` configuration
375 | - Verify Node.js installation
376 | - Use `--mcp-debug` flag when starting Claude Code
377 | - Use CLI as fallback if MCP unavailable
378 |
379 | ### Task File Sync Issues
380 |
381 | ```bash
382 | # Regenerate task files from tasks.json
383 | task-master generate
384 |
385 | # Fix dependency issues
386 | task-master fix-dependencies
387 | ```
388 |
389 | DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same
390 | Taskmaster core files.
391 |
392 | ## Important Notes
393 |
394 | ### AI-Powered Operations
395 |
396 | These commands make AI calls and may take up to a minute:
397 |
398 | - `parse_prd` / `task-master parse-prd`
399 | - `analyze_project_complexity` / `task-master analyze-complexity`
400 | - `expand_task` / `task-master expand`
401 | - `expand_all` / `task-master expand --all`
402 | - `add_task` / `task-master add-task`
403 | - `update` / `task-master update`
404 | - `update_task` / `task-master update-task`
405 | - `update_subtask` / `task-master update-subtask`
406 |
407 | ### File Management
408 |
409 | - Never manually edit `tasks.json` - use commands instead
410 | - Never manually edit `.taskmaster/config.json` - use `task-master models`
411 | - Task markdown files in `tasks/` are auto-generated
412 | - Run `task-master generate` after manual changes to tasks.json
413 |
414 | ### Claude Code Session Management
415 |
416 | - Use `/clear` frequently to maintain focused context
417 | - Create custom slash commands for repeated Task Master workflows
418 | - Configure tool allowlist to streamline permissions
419 | - Use headless mode for automation: `claude -p "task-master next"`
420 |
421 | ### Multi-Task Updates
422 |
423 | - Use `update --from=` to update multiple future tasks
424 | - Use `update-task --id=` for single task updates
425 | - Use `update-subtask --id=` for implementation logging
426 |
427 | ### Research Mode
428 |
429 | - Add `--research` flag for research-based AI enhancement
430 | - Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in
431 | environment
432 | - Provides more informed task creation and updates
433 | - Recommended for complex technical tasks
434 |
435 | ---
436 |
437 | _This guide ensures Claude Code has immediate access to Task Master's essential
438 | functionality for agentic development workflows._
439 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firebase Authentication with Flutter & DDD
2 |
3 | This repository provides a comprehensive example of a Flutter application that implements Firebase Authentication using a Domain-Driven Design (DDD) architecture. It leverages modern Flutter technologies like Hooks Riverpod for state management and Freezed for immutable data classes.
4 |
5 | This project serves as a robust starting point for building scalable and maintainable Flutter applications.
6 |
7 | ## Features
8 |
9 | * **Firebase Authentication:** Email & Password sign-in.
10 | * **Domain-Driven Design (DDD):** A clean and separated architecture to promote maintainability and testability.
11 | * **Riverpod:** State management for a reactive and predictable state.
12 | * **Freezed:** Code generation for immutable data classes and unions.
13 | * **Error Handling:** Clear separation of domain and application failures.
14 |
15 | ## Project Structure
16 |
17 | The project follows a layered architecture inspired by Domain-Driven Design. For a detailed explanation, please see the [Architecture Guide](./guides/architecture.md).
18 |
19 | ```
20 | lib/
21 | ├── application/ # Application Layer (Use Cases/BLoCs)
22 | ├── domain/ # Domain Layer (Entities, Value Objects, Interfaces)
23 | ├── services/ # Infrastructure Layer (Firebase Implementation)
24 | └── screens/ # Presentation Layer (UI)
25 | ```
26 |
27 | ## Getting Started
28 |
29 | To get a local copy up and running, please follow the detailed instructions in the [Setup Guide](./guides/setup.md).
30 |
31 | ## Architecture
32 |
33 | This project is built with a strong emphasis on clean architecture and Domain-Driven Design principles. To understand the structure, layers, and design decisions, please refer to the [Architecture Guide](./guides/architecture.md).
34 |
35 | ## Contributing
36 |
37 | Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
38 |
39 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
40 |
41 | 1. Fork the Project
42 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
43 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
44 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
45 | 5. Open a Pull Request
46 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | - lib/generated_plugin_registrant.dart
6 | - lib/**.freezed.dart
7 | - lib/**.g.dart
8 | - lib/**.config.dart
9 | - lib/**.gen.dart
10 | errors:
11 | prefer_const_constructors: error
12 | annotate_overrides: error
13 | prefer_double_quotes: error
14 | sort_pub_dependencies: error
15 | invalid_annotation_target: ignore
16 |
17 |
18 |
19 | linter:
20 | rules:
21 | prefer_double_quotes: true
22 | sort_pub_dependencies: true
23 | annotate_overrides: true
24 | file_names: false
25 | overridden_fields: false
26 | prefer_single_quotes: false
27 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "com.example.firebase_authentication_flutter_ddd"
27 | compileSdkVersion flutter.compileSdkVersion
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 |
43 | defaultConfig {
44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
45 | applicationId "com.example.firebase_authentication_flutter_ddd"
46 | // You can update the following values to match your application needs.
47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48 | minSdkVersion flutter.minSdkVersion
49 | targetSdkVersion flutter.targetSdkVersion
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | }
53 |
54 | buildTypes {
55 | release {
56 | // TODO: Add your own signing config for the release build.
57 | // Signing with the debug keys for now, so `flutter run --release` works.
58 | signingConfig signingConfigs.debug
59 | }
60 | }
61 | }
62 |
63 | flutter {
64 | source '../..'
65 | }
66 |
67 | dependencies {}
68 |
--------------------------------------------------------------------------------
/android/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("kotlin-android")
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id("dev.flutter.flutter-gradle-plugin")
6 | }
7 |
8 | android {
9 | namespace = "com.example.firebase_auth_flutter_ddd"
10 | compileSdk = flutter.compileSdkVersion
11 | ndkVersion = flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_11
15 | targetCompatibility = JavaVersion.VERSION_11
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_11.toString()
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "com.example.firebase_auth_flutter_ddd"
25 | // You can update the following values to match your application needs.
26 | // For more information, see: https://flutter.dev/to/review-gradle-config.
27 | minSdk = flutter.minSdkVersion
28 | targetSdk = flutter.targetSdkVersion
29 | versionCode = flutter.versionCode
30 | versionName = flutter.versionName
31 | }
32 |
33 | buildTypes {
34 | release {
35 | // TODO: Add your own signing config for the release build.
36 | // Signing with the debug keys for now, so `flutter run --release` works.
37 | signingConfig = signingConfigs.getByName("debug")
38 | }
39 | }
40 | }
41 |
42 | flutter {
43 | source = "../.."
44 | }
45 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/firebase_auth_flutter_ddd/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebase_auth_flutter_ddd
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
6 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/firebase_authentication_flutter_ddd/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebase_authentication_flutter_ddd
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 |
20 | rootProject.buildDir = '../build'
21 | subprojects {
22 | project.buildDir = "${rootProject.buildDir}/${project.name}"
23 | }
24 | subprojects {
25 | project.evaluationDependsOn(':app')
26 | }
27 |
28 | tasks.register("clean", Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
9 | rootProject.layout.buildDirectory.value(newBuildDir)
10 |
11 | subprojects {
12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
13 | project.layout.buildDirectory.value(newSubprojectBuildDir)
14 | }
15 | subprojects {
16 | project.evaluationDependsOn(":app")
17 | }
18 |
19 | tasks.register("clean") {
20 | delete(rootProject.layout.buildDirectory)
21 | }
22 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
21 | }
22 | }
23 |
24 | plugins {
25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
26 | id "com.android.application" version "7.3.0" apply false
27 | }
28 |
29 | include ":app"
30 |
--------------------------------------------------------------------------------
/android/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | val flutterSdkPath = run {
3 | val properties = java.util.Properties()
4 | file("local.properties").inputStream().use { properties.load(it) }
5 | val flutterSdkPath = properties.getProperty("flutter.sdk")
6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
7 | flutterSdkPath
8 | }
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0"
21 | id("com.android.application") version "8.7.3" apply false
22 | id("org.jetbrains.kotlin.android") version "2.1.0" apply false
23 | }
24 |
25 | include(":app")
26 |
--------------------------------------------------------------------------------
/guides/architecture.md:
--------------------------------------------------------------------------------
1 | # Architecture Overview
2 |
3 | This project follows the principles of Domain-Driven Design (DDD) to create a scalable and maintainable Flutter application. The architecture is divided into several layers, each with its own distinct responsibilities.
4 |
5 | ## Layers
6 |
7 | The project is structured into the following layers:
8 |
9 | * **Domain:** This is the core of the application and is independent of any other layer. It contains the business logic, entities, and value objects.
10 | * **Application:** This layer orchestrates the domain layer and is responsible for handling application-specific use cases. It contains the application services and data transfer objects (DTOs).
11 | * **Services (Infrastructure):** This layer is responsible for implementing the interfaces defined in the domain layer. It contains the implementation of the repositories and other external services.
12 | * **Screens (Presentation):** This is the UI layer of the application. It is responsible for displaying the data to the user and handling user input.
13 |
14 | ## Directory Structure
15 |
16 | The project's directory structure reflects the layered architecture:
17 |
18 | ```
19 | lib
20 | ├── application
21 | │ └── authentication
22 | ├── domain
23 | │ ├── authentication
24 | │ └── core
25 | ├── screens
26 | │ └── utils
27 | └── services
28 | └── authentication
29 | ```
30 |
31 | ### Domain Layer
32 |
33 | The `domain` directory contains the following:
34 |
35 | * **`authentication`:** This directory contains the core authentication-related business logic, including:
36 | * `auth_failures.dart`: Defines the possible failures that can occur during authentication.
37 | * `i_auth_facade.dart`: Defines the interface for the authentication service.
38 | * `auth_value_objects.dart`: Defines the value objects used in the authentication domain.
39 | * **`core`:** This directory contains the core building blocks of the domain layer, such as the `ValueObject` and `Error` classes.
40 |
41 | ### Application Layer
42 |
43 | The `application` directory contains the following:
44 |
45 | * **`authentication`:** This directory contains the application-level logic for authentication, including:
46 | * `auth_events.dart`: Defines the events that can be triggered by the UI.
47 | * `auth_state_controller.dart`: The BLoC (or Riverpod equivalent) that manages the state of the authentication flow.
48 | * `auth_states.dart`: Defines the different states of the authentication flow.
49 |
50 | ### Services (Infrastructure) Layer
51 |
52 | The `services` directory contains the following:
53 |
54 | * **`authentication`:** This directory contains the implementation of the `IAuthFacade` interface, which interacts with Firebase Authentication.
55 |
56 | ### Screens (Presentation) Layer
57 |
58 | The `screens` directory contains the following:
59 |
60 | * **`login_page.dart`:** The UI for the login screen.
61 | * **`home_page.dart`:** The UI for the home screen, which is displayed after the user has successfully authenticated.
62 | * **`utils`:** This directory contains utility widgets and functions used across the UI.
63 |
64 | ## State Management
65 |
66 | This project uses **Riverpod** for state management. The `auth_state_controller.dart` file in the `application` layer is responsible for managing the state of the authentication flow.
67 |
68 | ## Code Generation
69 |
70 | This project uses the **Freezed** package to generate immutable classes and unions. The `build_runner` package is used to run the code generation.
71 |
--------------------------------------------------------------------------------
/guides/setup.md:
--------------------------------------------------------------------------------
1 | # Project Setup Guide
2 |
3 | This guide will walk you through setting up the project on your local machine.
4 |
5 | ## Prerequisites
6 |
7 | Before you begin, ensure you have the following installed:
8 |
9 | * **Flutter SDK:** Make sure you have the Flutter SDK installed and configured correctly. You can find instructions on the [official Flutter website](https://flutter.dev/docs/get-started/install).
10 | * **Firebase Account:** You will need a Firebase account to connect the application to a Firebase project.
11 |
12 | ## Setup Steps
13 |
14 | 1. **Clone the Repository:**
15 |
16 | ```bash
17 | git clone https://github.com/your-username/firebase_authentication_flutter_ddd.git
18 | cd firebase_authentication_flutter_ddd
19 | ```
20 |
21 | 2. **Configure Firebase:**
22 |
23 | * Create a new Firebase project on the [Firebase Console](https://console.firebase.google.com/).
24 | * Follow the instructions to add a new Flutter application to your project.
25 | * Download the `google-services.json` file for Android and the `GoogleService-Info.plist` file for iOS.
26 | * Place the `google-services.json` file in the `android/app` directory.
27 | * Place the `GoogleService-Info.plist` file in the `ios/Runner` directory.
28 |
29 | 3. **Set Up Environment Variables:**
30 |
31 | * Create a `.env` file in the root of the project by copying the `.env.example` file:
32 |
33 | ```bash
34 | cp .env.example .env
35 | ```
36 |
37 | * Open the `.env` file and add the necessary API keys. For this project, you will need to add your `OLLAMA_API_KEY`.
38 |
39 | 4. **Install Dependencies:**
40 |
41 | * Run the following command to install the project's dependencies:
42 |
43 | ```bash
44 | flutter pub get
45 | ```
46 |
47 | 5. **Run the Application:**
48 |
49 | * You can now run the application on an emulator or a physical device:
50 |
51 | ```bash
52 | flutter run
53 | ```
54 |
55 | ## Build Runner
56 |
57 | This project uses the `build_runner` package to generate code. If you make any changes to the models or other generated files, you will need to run the following command:
58 |
59 | ```bash
60 | flutter pub run build_runner build
61 | ```
62 |
63 | This will regenerate the necessary files.
64 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '14.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Firebase/Auth (11.15.0):
3 | - Firebase/CoreOnly
4 | - FirebaseAuth (~> 11.15.0)
5 | - Firebase/CoreOnly (11.15.0):
6 | - FirebaseCore (~> 11.15.0)
7 | - firebase_auth (5.7.0):
8 | - Firebase/Auth (= 11.15.0)
9 | - firebase_core
10 | - Flutter
11 | - firebase_core (3.15.2):
12 | - Firebase/CoreOnly (= 11.15.0)
13 | - Flutter
14 | - FirebaseAppCheckInterop (11.15.0)
15 | - FirebaseAuth (11.15.0):
16 | - FirebaseAppCheckInterop (~> 11.0)
17 | - FirebaseAuthInterop (~> 11.0)
18 | - FirebaseCore (~> 11.15.0)
19 | - FirebaseCoreExtension (~> 11.15.0)
20 | - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
21 | - GoogleUtilities/Environment (~> 8.1)
22 | - GTMSessionFetcher/Core (< 5.0, >= 3.4)
23 | - RecaptchaInterop (~> 101.0)
24 | - FirebaseAuthInterop (11.15.0)
25 | - FirebaseCore (11.15.0):
26 | - FirebaseCoreInternal (~> 11.15.0)
27 | - GoogleUtilities/Environment (~> 8.1)
28 | - GoogleUtilities/Logger (~> 8.1)
29 | - FirebaseCoreExtension (11.15.0):
30 | - FirebaseCore (~> 11.15.0)
31 | - FirebaseCoreInternal (11.15.0):
32 | - "GoogleUtilities/NSData+zlib (~> 8.1)"
33 | - Flutter (1.0.0)
34 | - GoogleUtilities/AppDelegateSwizzler (8.1.0):
35 | - GoogleUtilities/Environment
36 | - GoogleUtilities/Logger
37 | - GoogleUtilities/Network
38 | - GoogleUtilities/Privacy
39 | - GoogleUtilities/Environment (8.1.0):
40 | - GoogleUtilities/Privacy
41 | - GoogleUtilities/Logger (8.1.0):
42 | - GoogleUtilities/Environment
43 | - GoogleUtilities/Privacy
44 | - GoogleUtilities/Network (8.1.0):
45 | - GoogleUtilities/Logger
46 | - "GoogleUtilities/NSData+zlib"
47 | - GoogleUtilities/Privacy
48 | - GoogleUtilities/Reachability
49 | - "GoogleUtilities/NSData+zlib (8.1.0)":
50 | - GoogleUtilities/Privacy
51 | - GoogleUtilities/Privacy (8.1.0)
52 | - GoogleUtilities/Reachability (8.1.0):
53 | - GoogleUtilities/Logger
54 | - GoogleUtilities/Privacy
55 | - GTMSessionFetcher/Core (4.5.0)
56 | - RecaptchaInterop (101.0.0)
57 |
58 | DEPENDENCIES:
59 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
60 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
61 | - Flutter (from `Flutter`)
62 |
63 | SPEC REPOS:
64 | trunk:
65 | - Firebase
66 | - FirebaseAppCheckInterop
67 | - FirebaseAuth
68 | - FirebaseAuthInterop
69 | - FirebaseCore
70 | - FirebaseCoreExtension
71 | - FirebaseCoreInternal
72 | - GoogleUtilities
73 | - GTMSessionFetcher
74 | - RecaptchaInterop
75 |
76 | EXTERNAL SOURCES:
77 | firebase_auth:
78 | :path: ".symlinks/plugins/firebase_auth/ios"
79 | firebase_core:
80 | :path: ".symlinks/plugins/firebase_core/ios"
81 | Flutter:
82 | :path: Flutter
83 |
84 | SPEC CHECKSUMS:
85 | Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
86 | firebase_auth: 5342db41af2ba5ed32a6177d9e326eecbebda912
87 | firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c
88 | FirebaseAppCheckInterop: 06fe5a3799278ae4667e6c432edd86b1030fa3df
89 | FirebaseAuth: a6575e5fbf46b046c58dc211a28a5fbdd8d4c83b
90 | FirebaseAuthInterop: 7087d7a4ee4bc4de019b2d0c240974ed5d89e2fd
91 | FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
92 | FirebaseCoreExtension: edbd30474b5ccf04e5f001470bdf6ea616af2435
93 | FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
94 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
95 | GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
96 | GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf
97 | RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
98 |
99 | PODFILE CHECKSUM: 1959d098c91d8a792531a723c4a9d7e9f6a01e38
100 |
101 | COCOAPODS: 1.16.2
102 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
44 |
50 |
51 |
52 |
53 |
54 |
66 |
68 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonhubdev/flutter_ddd_firebase_auth/10b311606657916f4397ad1618aae9812a67f24d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | 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.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Firebase Authentication Flutter Ddd
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | firebase_authentication_flutter_ddd
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/lib/app.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter/foundation.dart";
2 | import "package:flutter/material.dart";
3 | import "package:flutter/services.dart";
4 |
5 | import "core/theme/app_theme.dart";
6 | import "screens/login_page.dart";
7 |
8 | class FirebaseAuthenticationDDD extends StatelessWidget {
9 | const FirebaseAuthenticationDDD({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | SystemChrome.setSystemUIOverlayStyle(
14 | const SystemUiOverlayStyle(
15 | statusBarColor: Colors.transparent,
16 | statusBarIconBrightness: Brightness.dark,
17 | statusBarBrightness: Brightness.light,
18 | systemNavigationBarColor: Colors.transparent,
19 | systemNavigationBarDividerColor: Colors.transparent,
20 | systemNavigationBarIconBrightness: Brightness.dark,
21 | ),
22 | );
23 |
24 | return MaterialApp(
25 | title: "Firebase Auth DDD",
26 | debugShowCheckedModeBanner: kDebugMode,
27 |
28 | theme: AppTheme.lightTheme,
29 | darkTheme: AppTheme.darkTheme,
30 | themeMode: ThemeMode.system,
31 |
32 | home: const LoginPage(),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/application/authentication/auth_events.dart:
--------------------------------------------------------------------------------
1 | import "package:freezed_annotation/freezed_annotation.dart";
2 |
3 | part "auth_events.freezed.dart";
4 |
5 | @freezed
6 | class AuthEvents with _$AuthEvents {
7 | const factory AuthEvents.emailChanged({required String? email}) = EmailChanged;
8 |
9 | const factory AuthEvents.passwordChanged({required String? password}) = PasswordChanged;
10 |
11 | const factory AuthEvents.signUpWithEmailAndPasswordPressed() = SignUPWithEmailAndPasswordPressed;
12 |
13 | const factory AuthEvents.signInWithEmailAndPasswordPressed() = SignInWithEmailAndPasswordPressed;
14 | }
15 |
--------------------------------------------------------------------------------
/lib/application/authentication/auth_events.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // coverage:ignore-file
3 | // ignore_for_file: type=lint
4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
5 |
6 | part of 'auth_events.dart';
7 |
8 | // **************************************************************************
9 | // FreezedGenerator
10 | // **************************************************************************
11 |
12 | // dart format off
13 | T _$identity(T value) => value;
14 |
15 | /// @nodoc
16 | mixin _$AuthEvents {
17 | @override
18 | bool operator ==(Object other) {
19 | return identical(this, other) || (other.runtimeType == runtimeType && other is AuthEvents);
20 | }
21 |
22 | @override
23 | int get hashCode => runtimeType.hashCode;
24 |
25 | @override
26 | String toString() {
27 | return 'AuthEvents()';
28 | }
29 | }
30 |
31 | /// @nodoc
32 | class $AuthEventsCopyWith<$Res> {
33 | $AuthEventsCopyWith(AuthEvents _, $Res Function(AuthEvents) __);
34 | }
35 |
36 | /// Adds pattern-matching-related methods to [AuthEvents].
37 | extension AuthEventsPatterns on AuthEvents {
38 | /// A variant of `map` that fallback to returning `orElse`.
39 | ///
40 | /// It is equivalent to doing:
41 | /// ```dart
42 | /// switch (sealedClass) {
43 | /// case final Subclass value:
44 | /// return ...;
45 | /// case _:
46 | /// return orElse();
47 | /// }
48 | /// ```
49 |
50 | @optionalTypeArgs
51 | TResult maybeMap({
52 | TResult Function(EmailChanged value)? emailChanged,
53 | TResult Function(PasswordChanged value)? passwordChanged,
54 | TResult Function(SignUPWithEmailAndPasswordPressed value)? signUpWithEmailAndPasswordPressed,
55 | TResult Function(SignInWithEmailAndPasswordPressed value)? signInWithEmailAndPasswordPressed,
56 | required TResult orElse(),
57 | }) {
58 | final _that = this;
59 | switch (_that) {
60 | case EmailChanged() when emailChanged != null:
61 | return emailChanged(_that);
62 | case PasswordChanged() when passwordChanged != null:
63 | return passwordChanged(_that);
64 | case SignUPWithEmailAndPasswordPressed() when signUpWithEmailAndPasswordPressed != null:
65 | return signUpWithEmailAndPasswordPressed(_that);
66 | case SignInWithEmailAndPasswordPressed() when signInWithEmailAndPasswordPressed != null:
67 | return signInWithEmailAndPasswordPressed(_that);
68 | case _:
69 | return orElse();
70 | }
71 | }
72 |
73 | /// A `switch`-like method, using callbacks.
74 | ///
75 | /// Callbacks receives the raw object, upcasted.
76 | /// It is equivalent to doing:
77 | /// ```dart
78 | /// switch (sealedClass) {
79 | /// case final Subclass value:
80 | /// return ...;
81 | /// case final Subclass2 value:
82 | /// return ...;
83 | /// }
84 | /// ```
85 |
86 | @optionalTypeArgs
87 | TResult map({
88 | required TResult Function(EmailChanged value) emailChanged,
89 | required TResult Function(PasswordChanged value) passwordChanged,
90 | required TResult Function(SignUPWithEmailAndPasswordPressed value) signUpWithEmailAndPasswordPressed,
91 | required TResult Function(SignInWithEmailAndPasswordPressed value) signInWithEmailAndPasswordPressed,
92 | }) {
93 | final _that = this;
94 | switch (_that) {
95 | case EmailChanged():
96 | return emailChanged(_that);
97 | case PasswordChanged():
98 | return passwordChanged(_that);
99 | case SignUPWithEmailAndPasswordPressed():
100 | return signUpWithEmailAndPasswordPressed(_that);
101 | case SignInWithEmailAndPasswordPressed():
102 | return signInWithEmailAndPasswordPressed(_that);
103 | case _:
104 | throw StateError('Unexpected subclass');
105 | }
106 | }
107 |
108 | /// A variant of `map` that fallback to returning `null`.
109 | ///
110 | /// It is equivalent to doing:
111 | /// ```dart
112 | /// switch (sealedClass) {
113 | /// case final Subclass value:
114 | /// return ...;
115 | /// case _:
116 | /// return null;
117 | /// }
118 | /// ```
119 |
120 | @optionalTypeArgs
121 | TResult? mapOrNull({
122 | TResult? Function(EmailChanged value)? emailChanged,
123 | TResult? Function(PasswordChanged value)? passwordChanged,
124 | TResult? Function(SignUPWithEmailAndPasswordPressed value)? signUpWithEmailAndPasswordPressed,
125 | TResult? Function(SignInWithEmailAndPasswordPressed value)? signInWithEmailAndPasswordPressed,
126 | }) {
127 | final _that = this;
128 | switch (_that) {
129 | case EmailChanged() when emailChanged != null:
130 | return emailChanged(_that);
131 | case PasswordChanged() when passwordChanged != null:
132 | return passwordChanged(_that);
133 | case SignUPWithEmailAndPasswordPressed() when signUpWithEmailAndPasswordPressed != null:
134 | return signUpWithEmailAndPasswordPressed(_that);
135 | case SignInWithEmailAndPasswordPressed() when signInWithEmailAndPasswordPressed != null:
136 | return signInWithEmailAndPasswordPressed(_that);
137 | case _:
138 | return null;
139 | }
140 | }
141 |
142 | /// A variant of `when` that fallback to an `orElse` callback.
143 | ///
144 | /// It is equivalent to doing:
145 | /// ```dart
146 | /// switch (sealedClass) {
147 | /// case Subclass(:final field):
148 | /// return ...;
149 | /// case _:
150 | /// return orElse();
151 | /// }
152 | /// ```
153 |
154 | @optionalTypeArgs
155 | TResult maybeWhen({
156 | TResult Function(String? email)? emailChanged,
157 | TResult Function(String? password)? passwordChanged,
158 | TResult Function()? signUpWithEmailAndPasswordPressed,
159 | TResult Function()? signInWithEmailAndPasswordPressed,
160 | required TResult orElse(),
161 | }) {
162 | final _that = this;
163 | switch (_that) {
164 | case EmailChanged() when emailChanged != null:
165 | return emailChanged(_that.email);
166 | case PasswordChanged() when passwordChanged != null:
167 | return passwordChanged(_that.password);
168 | case SignUPWithEmailAndPasswordPressed() when signUpWithEmailAndPasswordPressed != null:
169 | return signUpWithEmailAndPasswordPressed();
170 | case SignInWithEmailAndPasswordPressed() when signInWithEmailAndPasswordPressed != null:
171 | return signInWithEmailAndPasswordPressed();
172 | case _:
173 | return orElse();
174 | }
175 | }
176 |
177 | /// A `switch`-like method, using callbacks.
178 | ///
179 | /// As opposed to `map`, this offers destructuring.
180 | /// It is equivalent to doing:
181 | /// ```dart
182 | /// switch (sealedClass) {
183 | /// case Subclass(:final field):
184 | /// return ...;
185 | /// case Subclass2(:final field2):
186 | /// return ...;
187 | /// }
188 | /// ```
189 |
190 | @optionalTypeArgs
191 | TResult when({
192 | required TResult Function(String? email) emailChanged,
193 | required TResult Function(String? password) passwordChanged,
194 | required TResult Function() signUpWithEmailAndPasswordPressed,
195 | required TResult Function() signInWithEmailAndPasswordPressed,
196 | }) {
197 | final _that = this;
198 | switch (_that) {
199 | case EmailChanged():
200 | return emailChanged(_that.email);
201 | case PasswordChanged():
202 | return passwordChanged(_that.password);
203 | case SignUPWithEmailAndPasswordPressed():
204 | return signUpWithEmailAndPasswordPressed();
205 | case SignInWithEmailAndPasswordPressed():
206 | return signInWithEmailAndPasswordPressed();
207 | case _:
208 | throw StateError('Unexpected subclass');
209 | }
210 | }
211 |
212 | /// A variant of `when` that fallback to returning `null`
213 | ///
214 | /// It is equivalent to doing:
215 | /// ```dart
216 | /// switch (sealedClass) {
217 | /// case Subclass(:final field):
218 | /// return ...;
219 | /// case _:
220 | /// return null;
221 | /// }
222 | /// ```
223 |
224 | @optionalTypeArgs
225 | TResult? whenOrNull({
226 | TResult? Function(String? email)? emailChanged,
227 | TResult? Function(String? password)? passwordChanged,
228 | TResult? Function()? signUpWithEmailAndPasswordPressed,
229 | TResult? Function()? signInWithEmailAndPasswordPressed,
230 | }) {
231 | final _that = this;
232 | switch (_that) {
233 | case EmailChanged() when emailChanged != null:
234 | return emailChanged(_that.email);
235 | case PasswordChanged() when passwordChanged != null:
236 | return passwordChanged(_that.password);
237 | case SignUPWithEmailAndPasswordPressed() when signUpWithEmailAndPasswordPressed != null:
238 | return signUpWithEmailAndPasswordPressed();
239 | case SignInWithEmailAndPasswordPressed() when signInWithEmailAndPasswordPressed != null:
240 | return signInWithEmailAndPasswordPressed();
241 | case _:
242 | return null;
243 | }
244 | }
245 | }
246 |
247 | /// @nodoc
248 |
249 | class EmailChanged implements AuthEvents {
250 | const EmailChanged({required this.email});
251 |
252 | final String? email;
253 |
254 | /// Create a copy of AuthEvents
255 | /// with the given fields replaced by the non-null parameter values.
256 | @JsonKey(includeFromJson: false, includeToJson: false)
257 | @pragma('vm:prefer-inline')
258 | $EmailChangedCopyWith get copyWith => _$EmailChangedCopyWithImpl(this, _$identity);
259 |
260 | @override
261 | bool operator ==(Object other) {
262 | return identical(this, other) ||
263 | (other.runtimeType == runtimeType &&
264 | other is EmailChanged &&
265 | (identical(other.email, email) || other.email == email));
266 | }
267 |
268 | @override
269 | int get hashCode => Object.hash(runtimeType, email);
270 |
271 | @override
272 | String toString() {
273 | return 'AuthEvents.emailChanged(email: $email)';
274 | }
275 | }
276 |
277 | /// @nodoc
278 | abstract mixin class $EmailChangedCopyWith<$Res> implements $AuthEventsCopyWith<$Res> {
279 | factory $EmailChangedCopyWith(EmailChanged value, $Res Function(EmailChanged) _then) = _$EmailChangedCopyWithImpl;
280 | @useResult
281 | $Res call({String? email});
282 | }
283 |
284 | /// @nodoc
285 | class _$EmailChangedCopyWithImpl<$Res> implements $EmailChangedCopyWith<$Res> {
286 | _$EmailChangedCopyWithImpl(this._self, this._then);
287 |
288 | final EmailChanged _self;
289 | final $Res Function(EmailChanged) _then;
290 |
291 | /// Create a copy of AuthEvents
292 | /// with the given fields replaced by the non-null parameter values.
293 | @pragma('vm:prefer-inline')
294 | $Res call({
295 | Object? email = freezed,
296 | }) {
297 | return _then(EmailChanged(
298 | email: freezed == email
299 | ? _self.email
300 | : email // ignore: cast_nullable_to_non_nullable
301 | as String?,
302 | ));
303 | }
304 | }
305 |
306 | /// @nodoc
307 |
308 | class PasswordChanged implements AuthEvents {
309 | const PasswordChanged({required this.password});
310 |
311 | final String? password;
312 |
313 | /// Create a copy of AuthEvents
314 | /// with the given fields replaced by the non-null parameter values.
315 | @JsonKey(includeFromJson: false, includeToJson: false)
316 | @pragma('vm:prefer-inline')
317 | $PasswordChangedCopyWith get copyWith =>
318 | _$PasswordChangedCopyWithImpl(this, _$identity);
319 |
320 | @override
321 | bool operator ==(Object other) {
322 | return identical(this, other) ||
323 | (other.runtimeType == runtimeType &&
324 | other is PasswordChanged &&
325 | (identical(other.password, password) || other.password == password));
326 | }
327 |
328 | @override
329 | int get hashCode => Object.hash(runtimeType, password);
330 |
331 | @override
332 | String toString() {
333 | return 'AuthEvents.passwordChanged(password: $password)';
334 | }
335 | }
336 |
337 | /// @nodoc
338 | abstract mixin class $PasswordChangedCopyWith<$Res> implements $AuthEventsCopyWith<$Res> {
339 | factory $PasswordChangedCopyWith(PasswordChanged value, $Res Function(PasswordChanged) _then) =
340 | _$PasswordChangedCopyWithImpl;
341 | @useResult
342 | $Res call({String? password});
343 | }
344 |
345 | /// @nodoc
346 | class _$PasswordChangedCopyWithImpl<$Res> implements $PasswordChangedCopyWith<$Res> {
347 | _$PasswordChangedCopyWithImpl(this._self, this._then);
348 |
349 | final PasswordChanged _self;
350 | final $Res Function(PasswordChanged) _then;
351 |
352 | /// Create a copy of AuthEvents
353 | /// with the given fields replaced by the non-null parameter values.
354 | @pragma('vm:prefer-inline')
355 | $Res call({
356 | Object? password = freezed,
357 | }) {
358 | return _then(PasswordChanged(
359 | password: freezed == password
360 | ? _self.password
361 | : password // ignore: cast_nullable_to_non_nullable
362 | as String?,
363 | ));
364 | }
365 | }
366 |
367 | /// @nodoc
368 |
369 | class SignUPWithEmailAndPasswordPressed implements AuthEvents {
370 | const SignUPWithEmailAndPasswordPressed();
371 |
372 | @override
373 | bool operator ==(Object other) {
374 | return identical(this, other) || (other.runtimeType == runtimeType && other is SignUPWithEmailAndPasswordPressed);
375 | }
376 |
377 | @override
378 | int get hashCode => runtimeType.hashCode;
379 |
380 | @override
381 | String toString() {
382 | return 'AuthEvents.signUpWithEmailAndPasswordPressed()';
383 | }
384 | }
385 |
386 | /// @nodoc
387 |
388 | class SignInWithEmailAndPasswordPressed implements AuthEvents {
389 | const SignInWithEmailAndPasswordPressed();
390 |
391 | @override
392 | bool operator ==(Object other) {
393 | return identical(this, other) || (other.runtimeType == runtimeType && other is SignInWithEmailAndPasswordPressed);
394 | }
395 |
396 | @override
397 | int get hashCode => runtimeType.hashCode;
398 |
399 | @override
400 | String toString() {
401 | return 'AuthEvents.signInWithEmailAndPasswordPressed()';
402 | }
403 | }
404 |
405 | // dart format on
406 |
--------------------------------------------------------------------------------
/lib/application/authentication/auth_state_controller.dart:
--------------------------------------------------------------------------------
1 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_failures.dart";
2 | import "package:flutter_riverpod/flutter_riverpod.dart";
3 | import "package:fpdart/fpdart.dart";
4 |
5 | import "../../domain/authentication/auth_value_objects.dart";
6 | import "../../domain/authentication/i_auth_facade.dart";
7 | import "../../services/authentication/firebase_auth_facade.dart";
8 | import "auth_events.dart";
9 | import "auth_states.dart";
10 |
11 | final authStateControllerProvider = NotifierProvider(
12 | AuthStateController.new,
13 | );
14 |
15 | class AuthStateController extends Notifier {
16 | @override
17 | AuthStates build() {
18 | return AuthStates.initial();
19 | }
20 |
21 | IAuthFacade get _authFacade => ref.read(firebaseAuthFacadeProvider);
22 |
23 | Future mapEventsToStates(AuthEvents events) async {
24 | await events.map(
25 | emailChanged: (value) async {
26 | _updateEmail(value.email!);
27 | },
28 | passwordChanged: (value) async {
29 | _updatePassword(value.password!);
30 | },
31 | signUpWithEmailAndPasswordPressed: (value) async {
32 | await signUpWithEmailAndPassword();
33 | },
34 | signInWithEmailAndPasswordPressed: (value) async {
35 | await signInWithEmailAndPassword();
36 | },
37 | );
38 | }
39 |
40 | void emailChanged(String email) {
41 | _updateEmail(email);
42 | }
43 |
44 | void passwordChanged(String password) {
45 | _updatePassword(password);
46 | }
47 |
48 | void _updateEmail(String email) {
49 | state = state.copyWith(
50 | emailAddress: EmailAddress(email: email),
51 | authFailureOrSuccess: none(),
52 | showError: false,
53 | );
54 | }
55 |
56 | void _updatePassword(String password) {
57 | state = state.copyWith(
58 | password: Password(password: password),
59 | authFailureOrSuccess: none(),
60 | showError: false,
61 | );
62 | }
63 |
64 | Future signUpWithEmailAndPassword() async {
65 | await _performAuthAction(_authFacade.registerWithEmailAndPassword);
66 | }
67 |
68 | Future signInWithEmailAndPassword() async {
69 | await _performAuthAction(_authFacade.signInWithEmailAndPassword);
70 | }
71 |
72 | Future _performAuthAction(
73 | Future> Function({
74 | required EmailAddress emailAddress,
75 | required Password password,
76 | }) forwardCall,
77 | ) async {
78 | final isEmailValid = state.emailAddress.isValid();
79 | final isPasswordValid = state.password.isValid();
80 |
81 | if (!isEmailValid || !isPasswordValid) {
82 | state = state.copyWith(
83 | showError: true,
84 | authFailureOrSuccess: none(),
85 | );
86 | return;
87 | }
88 |
89 | state = state.copyWith(
90 | isSubmitting: true,
91 | authFailureOrSuccess: none(),
92 | showError: false,
93 | );
94 |
95 | final failureOrSuccess = await forwardCall(
96 | emailAddress: state.emailAddress,
97 | password: state.password,
98 | );
99 |
100 | state = state.copyWith(
101 | isSubmitting: false,
102 | showError: failureOrSuccess.isLeft(),
103 | authFailureOrSuccess: optionOf(failureOrSuccess),
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/lib/application/authentication/auth_states.dart:
--------------------------------------------------------------------------------
1 | import "package:fpdart/fpdart.dart";
2 | import "package:freezed_annotation/freezed_annotation.dart";
3 |
4 | import "../../domain/authentication/auth_failures.dart";
5 | import "../../domain/authentication/auth_value_objects.dart";
6 |
7 | part "auth_states.freezed.dart";
8 |
9 | @freezed
10 | sealed class AuthStates with _$AuthStates {
11 | const factory AuthStates({
12 | required EmailAddress emailAddress,
13 | required Password password,
14 | required bool isSubmitting,
15 | required bool showError,
16 | required Option> authFailureOrSuccess,
17 | }) = _AuthStates;
18 |
19 | factory AuthStates.initial() => AuthStates(
20 | emailAddress: EmailAddress(email: ""),
21 | password: Password(password: ""),
22 | showError: false,
23 | isSubmitting: false,
24 | authFailureOrSuccess: none());
25 | }
26 |
--------------------------------------------------------------------------------
/lib/application/authentication/auth_states.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // coverage:ignore-file
3 | // ignore_for_file: type=lint
4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
5 |
6 | part of 'auth_states.dart';
7 |
8 | // **************************************************************************
9 | // FreezedGenerator
10 | // **************************************************************************
11 |
12 | // dart format off
13 | T _$identity(T value) => value;
14 |
15 | /// @nodoc
16 | mixin _$AuthStates {
17 | EmailAddress get emailAddress;
18 | Password get password;
19 | bool get isSubmitting;
20 | bool get showError;
21 | Option> get authFailureOrSuccess;
22 |
23 | /// Create a copy of AuthStates
24 | /// with the given fields replaced by the non-null parameter values.
25 | @JsonKey(includeFromJson: false, includeToJson: false)
26 | @pragma('vm:prefer-inline')
27 | $AuthStatesCopyWith get copyWith =>
28 | _$AuthStatesCopyWithImpl(this as AuthStates, _$identity);
29 |
30 | @override
31 | bool operator ==(Object other) {
32 | return identical(this, other) ||
33 | (other.runtimeType == runtimeType &&
34 | other is AuthStates &&
35 | (identical(other.emailAddress, emailAddress) ||
36 | other.emailAddress == emailAddress) &&
37 | (identical(other.password, password) ||
38 | other.password == password) &&
39 | (identical(other.isSubmitting, isSubmitting) ||
40 | other.isSubmitting == isSubmitting) &&
41 | (identical(other.showError, showError) ||
42 | other.showError == showError) &&
43 | (identical(other.authFailureOrSuccess, authFailureOrSuccess) ||
44 | other.authFailureOrSuccess == authFailureOrSuccess));
45 | }
46 |
47 | @override
48 | int get hashCode => Object.hash(runtimeType, emailAddress, password,
49 | isSubmitting, showError, authFailureOrSuccess);
50 |
51 | @override
52 | String toString() {
53 | return 'AuthStates(emailAddress: $emailAddress, password: $password, isSubmitting: $isSubmitting, showError: $showError, authFailureOrSuccess: $authFailureOrSuccess)';
54 | }
55 | }
56 |
57 | /// @nodoc
58 | abstract mixin class $AuthStatesCopyWith<$Res> {
59 | factory $AuthStatesCopyWith(
60 | AuthStates value, $Res Function(AuthStates) _then) =
61 | _$AuthStatesCopyWithImpl;
62 | @useResult
63 | $Res call(
64 | {EmailAddress emailAddress,
65 | Password password,
66 | bool isSubmitting,
67 | bool showError,
68 | Option> authFailureOrSuccess});
69 | }
70 |
71 | /// @nodoc
72 | class _$AuthStatesCopyWithImpl<$Res> implements $AuthStatesCopyWith<$Res> {
73 | _$AuthStatesCopyWithImpl(this._self, this._then);
74 |
75 | final AuthStates _self;
76 | final $Res Function(AuthStates) _then;
77 |
78 | /// Create a copy of AuthStates
79 | /// with the given fields replaced by the non-null parameter values.
80 | @pragma('vm:prefer-inline')
81 | @override
82 | $Res call({
83 | Object? emailAddress = null,
84 | Object? password = null,
85 | Object? isSubmitting = null,
86 | Object? showError = null,
87 | Object? authFailureOrSuccess = null,
88 | }) {
89 | return _then(_self.copyWith(
90 | emailAddress: null == emailAddress
91 | ? _self.emailAddress
92 | : emailAddress // ignore: cast_nullable_to_non_nullable
93 | as EmailAddress,
94 | password: null == password
95 | ? _self.password
96 | : password // ignore: cast_nullable_to_non_nullable
97 | as Password,
98 | isSubmitting: null == isSubmitting
99 | ? _self.isSubmitting
100 | : isSubmitting // ignore: cast_nullable_to_non_nullable
101 | as bool,
102 | showError: null == showError
103 | ? _self.showError
104 | : showError // ignore: cast_nullable_to_non_nullable
105 | as bool,
106 | authFailureOrSuccess: null == authFailureOrSuccess
107 | ? _self.authFailureOrSuccess
108 | : authFailureOrSuccess // ignore: cast_nullable_to_non_nullable
109 | as Option>,
110 | ));
111 | }
112 | }
113 |
114 | /// Adds pattern-matching-related methods to [AuthStates].
115 | extension AuthStatesPatterns on AuthStates {
116 | /// A variant of `map` that fallback to returning `orElse`.
117 | ///
118 | /// It is equivalent to doing:
119 | /// ```dart
120 | /// switch (sealedClass) {
121 | /// case final Subclass value:
122 | /// return ...;
123 | /// case _:
124 | /// return orElse();
125 | /// }
126 | /// ```
127 |
128 | @optionalTypeArgs
129 | TResult maybeMap(
130 | TResult Function(_AuthStates value)? $default, {
131 | required TResult orElse(),
132 | }) {
133 | final _that = this;
134 | switch (_that) {
135 | case _AuthStates() when $default != null:
136 | return $default(_that);
137 | case _:
138 | return orElse();
139 | }
140 | }
141 |
142 | /// A `switch`-like method, using callbacks.
143 | ///
144 | /// Callbacks receives the raw object, upcasted.
145 | /// It is equivalent to doing:
146 | /// ```dart
147 | /// switch (sealedClass) {
148 | /// case final Subclass value:
149 | /// return ...;
150 | /// case final Subclass2 value:
151 | /// return ...;
152 | /// }
153 | /// ```
154 |
155 | @optionalTypeArgs
156 | TResult map(
157 | TResult Function(_AuthStates value) $default,
158 | ) {
159 | final _that = this;
160 | switch (_that) {
161 | case _AuthStates():
162 | return $default(_that);
163 | }
164 | }
165 |
166 | /// A variant of `map` that fallback to returning `null`.
167 | ///
168 | /// It is equivalent to doing:
169 | /// ```dart
170 | /// switch (sealedClass) {
171 | /// case final Subclass value:
172 | /// return ...;
173 | /// case _:
174 | /// return null;
175 | /// }
176 | /// ```
177 |
178 | @optionalTypeArgs
179 | TResult? mapOrNull(
180 | TResult? Function(_AuthStates value)? $default,
181 | ) {
182 | final _that = this;
183 | switch (_that) {
184 | case _AuthStates() when $default != null:
185 | return $default(_that);
186 | case _:
187 | return null;
188 | }
189 | }
190 |
191 | /// A variant of `when` that fallback to an `orElse` callback.
192 | ///
193 | /// It is equivalent to doing:
194 | /// ```dart
195 | /// switch (sealedClass) {
196 | /// case Subclass(:final field):
197 | /// return ...;
198 | /// case _:
199 | /// return orElse();
200 | /// }
201 | /// ```
202 |
203 | @optionalTypeArgs
204 | TResult maybeWhen(
205 | TResult Function(
206 | EmailAddress emailAddress,
207 | Password password,
208 | bool isSubmitting,
209 | bool showError,
210 | Option> authFailureOrSuccess)?
211 | $default, {
212 | required TResult orElse(),
213 | }) {
214 | final _that = this;
215 | switch (_that) {
216 | case _AuthStates() when $default != null:
217 | return $default(_that.emailAddress, _that.password, _that.isSubmitting,
218 | _that.showError, _that.authFailureOrSuccess);
219 | case _:
220 | return orElse();
221 | }
222 | }
223 |
224 | /// A `switch`-like method, using callbacks.
225 | ///
226 | /// As opposed to `map`, this offers destructuring.
227 | /// It is equivalent to doing:
228 | /// ```dart
229 | /// switch (sealedClass) {
230 | /// case Subclass(:final field):
231 | /// return ...;
232 | /// case Subclass2(:final field2):
233 | /// return ...;
234 | /// }
235 | /// ```
236 |
237 | @optionalTypeArgs
238 | TResult when(
239 | TResult Function(
240 | EmailAddress emailAddress,
241 | Password password,
242 | bool isSubmitting,
243 | bool showError,
244 | Option> authFailureOrSuccess)
245 | $default,
246 | ) {
247 | final _that = this;
248 | switch (_that) {
249 | case _AuthStates():
250 | return $default(_that.emailAddress, _that.password, _that.isSubmitting,
251 | _that.showError, _that.authFailureOrSuccess);
252 | }
253 | }
254 |
255 | /// A variant of `when` that fallback to returning `null`
256 | ///
257 | /// It is equivalent to doing:
258 | /// ```dart
259 | /// switch (sealedClass) {
260 | /// case Subclass(:final field):
261 | /// return ...;
262 | /// case _:
263 | /// return null;
264 | /// }
265 | /// ```
266 |
267 | @optionalTypeArgs
268 | TResult? whenOrNull(
269 | TResult? Function(
270 | EmailAddress emailAddress,
271 | Password password,
272 | bool isSubmitting,
273 | bool showError,
274 | Option> authFailureOrSuccess)?
275 | $default,
276 | ) {
277 | final _that = this;
278 | switch (_that) {
279 | case _AuthStates() when $default != null:
280 | return $default(_that.emailAddress, _that.password, _that.isSubmitting,
281 | _that.showError, _that.authFailureOrSuccess);
282 | case _:
283 | return null;
284 | }
285 | }
286 | }
287 |
288 | /// @nodoc
289 |
290 | class _AuthStates implements AuthStates {
291 | const _AuthStates(
292 | {required this.emailAddress,
293 | required this.password,
294 | required this.isSubmitting,
295 | required this.showError,
296 | required this.authFailureOrSuccess});
297 |
298 | @override
299 | final EmailAddress emailAddress;
300 | @override
301 | final Password password;
302 | @override
303 | final bool isSubmitting;
304 | @override
305 | final bool showError;
306 | @override
307 | final Option> authFailureOrSuccess;
308 |
309 | /// Create a copy of AuthStates
310 | /// with the given fields replaced by the non-null parameter values.
311 | @override
312 | @JsonKey(includeFromJson: false, includeToJson: false)
313 | @pragma('vm:prefer-inline')
314 | _$AuthStatesCopyWith<_AuthStates> get copyWith =>
315 | __$AuthStatesCopyWithImpl<_AuthStates>(this, _$identity);
316 |
317 | @override
318 | bool operator ==(Object other) {
319 | return identical(this, other) ||
320 | (other.runtimeType == runtimeType &&
321 | other is _AuthStates &&
322 | (identical(other.emailAddress, emailAddress) ||
323 | other.emailAddress == emailAddress) &&
324 | (identical(other.password, password) ||
325 | other.password == password) &&
326 | (identical(other.isSubmitting, isSubmitting) ||
327 | other.isSubmitting == isSubmitting) &&
328 | (identical(other.showError, showError) ||
329 | other.showError == showError) &&
330 | (identical(other.authFailureOrSuccess, authFailureOrSuccess) ||
331 | other.authFailureOrSuccess == authFailureOrSuccess));
332 | }
333 |
334 | @override
335 | int get hashCode => Object.hash(runtimeType, emailAddress, password,
336 | isSubmitting, showError, authFailureOrSuccess);
337 |
338 | @override
339 | String toString() {
340 | return 'AuthStates(emailAddress: $emailAddress, password: $password, isSubmitting: $isSubmitting, showError: $showError, authFailureOrSuccess: $authFailureOrSuccess)';
341 | }
342 | }
343 |
344 | /// @nodoc
345 | abstract mixin class _$AuthStatesCopyWith<$Res>
346 | implements $AuthStatesCopyWith<$Res> {
347 | factory _$AuthStatesCopyWith(
348 | _AuthStates value, $Res Function(_AuthStates) _then) =
349 | __$AuthStatesCopyWithImpl;
350 | @override
351 | @useResult
352 | $Res call(
353 | {EmailAddress emailAddress,
354 | Password password,
355 | bool isSubmitting,
356 | bool showError,
357 | Option> authFailureOrSuccess});
358 | }
359 |
360 | /// @nodoc
361 | class __$AuthStatesCopyWithImpl<$Res> implements _$AuthStatesCopyWith<$Res> {
362 | __$AuthStatesCopyWithImpl(this._self, this._then);
363 |
364 | final _AuthStates _self;
365 | final $Res Function(_AuthStates) _then;
366 |
367 | /// Create a copy of AuthStates
368 | /// with the given fields replaced by the non-null parameter values.
369 | @override
370 | @pragma('vm:prefer-inline')
371 | $Res call({
372 | Object? emailAddress = null,
373 | Object? password = null,
374 | Object? isSubmitting = null,
375 | Object? showError = null,
376 | Object? authFailureOrSuccess = null,
377 | }) {
378 | return _then(_AuthStates(
379 | emailAddress: null == emailAddress
380 | ? _self.emailAddress
381 | : emailAddress // ignore: cast_nullable_to_non_nullable
382 | as EmailAddress,
383 | password: null == password
384 | ? _self.password
385 | : password // ignore: cast_nullable_to_non_nullable
386 | as Password,
387 | isSubmitting: null == isSubmitting
388 | ? _self.isSubmitting
389 | : isSubmitting // ignore: cast_nullable_to_non_nullable
390 | as bool,
391 | showError: null == showError
392 | ? _self.showError
393 | : showError // ignore: cast_nullable_to_non_nullable
394 | as bool,
395 | authFailureOrSuccess: null == authFailureOrSuccess
396 | ? _self.authFailureOrSuccess
397 | : authFailureOrSuccess // ignore: cast_nullable_to_non_nullable
398 | as Option>,
399 | ));
400 | }
401 | }
402 |
403 | // dart format on
404 |
--------------------------------------------------------------------------------
/lib/core/theme/animated_widgets.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter/material.dart";
2 |
3 | class AnimatedFormField extends StatefulWidget {
4 | final String label;
5 | final String? hint;
6 | final bool obscureText;
7 | final TextInputType keyboardType;
8 | final String? Function(String?)? validator;
9 | final void Function(String)? onChanged;
10 | final IconData? prefixIcon;
11 | final Widget? suffixIcon;
12 | final TextEditingController? controller;
13 |
14 | const AnimatedFormField({
15 | super.key,
16 | required this.label,
17 | this.hint,
18 | this.obscureText = false,
19 | this.keyboardType = TextInputType.text,
20 | this.validator,
21 | this.onChanged,
22 | this.prefixIcon,
23 | this.suffixIcon,
24 | this.controller,
25 | });
26 |
27 | @override
28 | State createState() => _AnimatedFormFieldState();
29 | }
30 |
31 | class _AnimatedFormFieldState extends State with SingleTickerProviderStateMixin {
32 | late AnimationController _animationController;
33 | late Animation _scaleAnimation;
34 | late Animation _opacityAnimation;
35 | bool _isFocused = false;
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | _animationController = AnimationController(
41 | duration: const Duration(milliseconds: 300),
42 | vsync: this,
43 | );
44 | _scaleAnimation = Tween(begin: 0.95, end: 1.0).animate(
45 | CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic),
46 | );
47 | _opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(
48 | CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
49 | );
50 | _animationController.forward();
51 | }
52 |
53 | @override
54 | void dispose() {
55 | _animationController.dispose();
56 | super.dispose();
57 | }
58 |
59 | @override
60 | Widget build(BuildContext context) {
61 | return AnimatedBuilder(
62 | animation: _animationController,
63 | builder: (context, child) {
64 | return Transform.scale(
65 | scale: _scaleAnimation.value,
66 | child: Opacity(
67 | opacity: _opacityAnimation.value,
68 | child: Focus(
69 | onFocusChange: (focused) {
70 | setState(() {
71 | _isFocused = focused;
72 | });
73 | },
74 | child: AnimatedContainer(
75 | duration: const Duration(milliseconds: 200),
76 | transform: Matrix4.identity()..scale(_isFocused ? 1.02 : 1.0),
77 | child: TextFormField(
78 | controller: widget.controller,
79 | obscureText: widget.obscureText,
80 | keyboardType: widget.keyboardType,
81 | validator: widget.validator,
82 | onChanged: widget.onChanged,
83 | decoration: InputDecoration(
84 | labelText: widget.label,
85 | hintText: widget.hint,
86 | prefixIcon: widget.prefixIcon != null ? Icon(widget.prefixIcon) : null,
87 | suffixIcon: widget.suffixIcon,
88 | ),
89 | ),
90 | ),
91 | ),
92 | ),
93 | );
94 | },
95 | );
96 | }
97 | }
98 |
99 | class AnimatedButton extends StatefulWidget {
100 | final String text;
101 | final VoidCallback? onPressed;
102 | final bool isLoading;
103 | final IconData? icon;
104 | final ButtonStyle? style;
105 |
106 | const AnimatedButton({
107 | super.key,
108 | required this.text,
109 | this.onPressed,
110 | this.isLoading = false,
111 | this.icon,
112 | this.style,
113 | });
114 |
115 | @override
116 | State createState() => _AnimatedButtonState();
117 | }
118 |
119 | class _AnimatedButtonState extends State with SingleTickerProviderStateMixin {
120 | late AnimationController _animationController;
121 | late Animation _scaleAnimation;
122 |
123 | @override
124 | void initState() {
125 | super.initState();
126 | _animationController = AnimationController(
127 | duration: const Duration(milliseconds: 150),
128 | vsync: this,
129 | );
130 | _scaleAnimation = Tween(begin: 1.0, end: 0.95).animate(
131 | CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
132 | );
133 | }
134 |
135 | @override
136 | void dispose() {
137 | _animationController.dispose();
138 | super.dispose();
139 | }
140 |
141 | @override
142 | Widget build(BuildContext context) {
143 | return AnimatedBuilder(
144 | animation: _animationController,
145 | builder: (context, child) {
146 | return Transform.scale(
147 | scale: _scaleAnimation.value,
148 | child: GestureDetector(
149 | onTapDown: (_) {
150 | _animationController.forward();
151 | },
152 | onTapUp: (_) {
153 | _animationController.reverse();
154 | },
155 | onTapCancel: () {
156 | _animationController.reverse();
157 | },
158 | child: ElevatedButton(
159 | onPressed: widget.isLoading ? null : widget.onPressed,
160 | style: widget.style,
161 | child: AnimatedSwitcher(
162 | duration: const Duration(milliseconds: 300),
163 | child: widget.isLoading
164 | ? const SizedBox(
165 | height: 20,
166 | width: 20,
167 | child: CircularProgressIndicator(strokeWidth: 2),
168 | )
169 | : Row(
170 | mainAxisSize: MainAxisSize.min,
171 | children: [
172 | if (widget.icon != null) ...[
173 | Icon(widget.icon),
174 | const SizedBox(width: 8),
175 | ],
176 | Text(widget.text),
177 | ],
178 | ),
179 | ),
180 | ),
181 | ),
182 | );
183 | },
184 | );
185 | }
186 | }
187 |
188 | class SlideInWidget extends StatefulWidget {
189 | final Widget child;
190 | final int delay;
191 | final Offset begin;
192 | final Duration duration;
193 |
194 | const SlideInWidget({
195 | super.key,
196 | required this.child,
197 | this.delay = 0,
198 | this.begin = const Offset(0, 0.3),
199 | this.duration = const Duration(milliseconds: 600),
200 | });
201 |
202 | @override
203 | State createState() => _SlideInWidgetState();
204 | }
205 |
206 | class _SlideInWidgetState extends State with SingleTickerProviderStateMixin {
207 | late AnimationController _animationController;
208 | late Animation _slideAnimation;
209 | late Animation _fadeAnimation;
210 |
211 | @override
212 | void initState() {
213 | super.initState();
214 | _animationController = AnimationController(
215 | duration: widget.duration,
216 | vsync: this,
217 | );
218 |
219 | _slideAnimation = Tween(
220 | begin: widget.begin,
221 | end: Offset.zero,
222 | ).animate(CurvedAnimation(
223 | parent: _animationController,
224 | curve: Curves.easeOutCubic,
225 | ));
226 |
227 | _fadeAnimation = Tween(
228 | begin: 0.0,
229 | end: 1.0,
230 | ).animate(CurvedAnimation(
231 | parent: _animationController,
232 | curve: Curves.easeOut,
233 | ));
234 |
235 | Future.delayed(Duration(milliseconds: widget.delay), () {
236 | if (mounted) {
237 | _animationController.forward();
238 | }
239 | });
240 | }
241 |
242 | @override
243 | void dispose() {
244 | _animationController.dispose();
245 | super.dispose();
246 | }
247 |
248 | @override
249 | Widget build(BuildContext context) {
250 | return AnimatedBuilder(
251 | animation: _animationController,
252 | builder: (context, child) {
253 | return SlideTransition(
254 | position: _slideAnimation,
255 | child: FadeTransition(
256 | opacity: _fadeAnimation,
257 | child: widget.child,
258 | ),
259 | );
260 | },
261 | );
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/lib/core/theme/app_theme.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter/material.dart";
2 | import "package:flutter/services.dart";
3 |
4 | class AppTheme {
5 | // Color Seed for Material You
6 | static const Color _primarySeed = Color(0xFF6750A4);
7 |
8 | // Light Theme
9 | static ThemeData get lightTheme {
10 | final ColorScheme lightColorScheme = ColorScheme.fromSeed(
11 | seedColor: _primarySeed,
12 | brightness: Brightness.light,
13 | );
14 |
15 | return ThemeData(
16 | useMaterial3: true,
17 | colorScheme: lightColorScheme,
18 |
19 | // App Bar Theme
20 | appBarTheme: AppBarTheme(
21 | elevation: 0,
22 | scrolledUnderElevation: 1,
23 | backgroundColor: lightColorScheme.surface,
24 | foregroundColor: lightColorScheme.onSurface,
25 | centerTitle: true,
26 | titleTextStyle: TextStyle(
27 | fontSize: 22,
28 | fontWeight: FontWeight.w500,
29 | color: lightColorScheme.onSurface,
30 | ),
31 | systemOverlayStyle: SystemUiOverlayStyle.dark,
32 | ),
33 |
34 | // Input Decoration Theme
35 | inputDecorationTheme: InputDecorationTheme(
36 | filled: true,
37 | fillColor: lightColorScheme.surfaceContainerHigh.withValues(alpha: 0.3),
38 | border: OutlineInputBorder(
39 | borderRadius: BorderRadius.circular(16),
40 | borderSide: BorderSide.none,
41 | ),
42 | enabledBorder: OutlineInputBorder(
43 | borderRadius: BorderRadius.circular(16),
44 | borderSide: BorderSide(
45 | color: lightColorScheme.outline.withValues(alpha: 0.3),
46 | width: 1,
47 | ),
48 | ),
49 | focusedBorder: OutlineInputBorder(
50 | borderRadius: BorderRadius.circular(16),
51 | borderSide: BorderSide(
52 | color: lightColorScheme.primary,
53 | width: 2,
54 | ),
55 | ),
56 | errorBorder: OutlineInputBorder(
57 | borderRadius: BorderRadius.circular(16),
58 | borderSide: BorderSide(
59 | color: lightColorScheme.error,
60 | width: 1,
61 | ),
62 | ),
63 | focusedErrorBorder: OutlineInputBorder(
64 | borderRadius: BorderRadius.circular(16),
65 | borderSide: BorderSide(
66 | color: lightColorScheme.error,
67 | width: 2,
68 | ),
69 | ),
70 | contentPadding: const EdgeInsets.all(20),
71 | labelStyle: TextStyle(
72 | color: lightColorScheme.onSurfaceVariant,
73 | fontSize: 16,
74 | ),
75 | hintStyle: TextStyle(
76 | color: lightColorScheme.onSurfaceVariant.withValues(alpha: 0.6),
77 | fontSize: 16,
78 | ),
79 | ),
80 |
81 | // Elevated Button Theme
82 | elevatedButtonTheme: ElevatedButtonThemeData(
83 | style: ElevatedButton.styleFrom(
84 | elevation: 0,
85 | backgroundColor: lightColorScheme.primary,
86 | foregroundColor: lightColorScheme.onPrimary,
87 | disabledBackgroundColor: lightColorScheme.onSurface.withValues(alpha: 0.12),
88 | disabledForegroundColor: lightColorScheme.onSurface.withValues(alpha: 0.38),
89 | padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
90 | minimumSize: const Size(0, 56),
91 | shape: RoundedRectangleBorder(
92 | borderRadius: BorderRadius.circular(16),
93 | ),
94 | textStyle: const TextStyle(
95 | fontSize: 16,
96 | fontWeight: FontWeight.w600,
97 | ),
98 | ),
99 | ),
100 |
101 | // Text Button Theme
102 | textButtonTheme: TextButtonThemeData(
103 | style: TextButton.styleFrom(
104 | foregroundColor: lightColorScheme.primary,
105 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
106 | minimumSize: const Size(0, 48),
107 | shape: RoundedRectangleBorder(
108 | borderRadius: BorderRadius.circular(12),
109 | ),
110 | textStyle: const TextStyle(
111 | fontSize: 16,
112 | fontWeight: FontWeight.w600,
113 | ),
114 | ),
115 | ),
116 |
117 | // Card Theme
118 | cardTheme: CardThemeData(
119 | elevation: 0,
120 | color: lightColorScheme.surfaceContainerHigh.withValues(alpha: 0.3),
121 | shape: RoundedRectangleBorder(
122 | borderRadius: BorderRadius.circular(20),
123 | side: BorderSide(
124 | color: lightColorScheme.outline.withValues(alpha: 0.2),
125 | width: 1,
126 | ),
127 | ),
128 | margin: const EdgeInsets.all(8),
129 | ),
130 |
131 | // Scaffold Background
132 | scaffoldBackgroundColor: lightColorScheme.surface,
133 | );
134 | }
135 |
136 | // Dark Theme
137 | static ThemeData get darkTheme {
138 | final ColorScheme darkColorScheme = ColorScheme.fromSeed(
139 | seedColor: _primarySeed,
140 | brightness: Brightness.dark,
141 | );
142 |
143 | return ThemeData(
144 | useMaterial3: true,
145 | colorScheme: darkColorScheme,
146 |
147 | // App Bar Theme
148 | appBarTheme: AppBarTheme(
149 | elevation: 0,
150 | scrolledUnderElevation: 1,
151 | backgroundColor: darkColorScheme.surface,
152 | foregroundColor: darkColorScheme.onSurface,
153 | centerTitle: true,
154 | titleTextStyle: TextStyle(
155 | fontSize: 22,
156 | fontWeight: FontWeight.w500,
157 | color: darkColorScheme.onSurface,
158 | ),
159 | systemOverlayStyle: SystemUiOverlayStyle.light,
160 | ),
161 |
162 | // Input Decoration Theme
163 | inputDecorationTheme: InputDecorationTheme(
164 | filled: true,
165 | fillColor: darkColorScheme.surfaceContainerHigh.withValues(alpha: 0.3),
166 | border: OutlineInputBorder(
167 | borderRadius: BorderRadius.circular(16),
168 | borderSide: BorderSide.none,
169 | ),
170 | enabledBorder: OutlineInputBorder(
171 | borderRadius: BorderRadius.circular(16),
172 | borderSide: BorderSide(
173 | color: darkColorScheme.outline.withValues(alpha: 0.3),
174 | width: 1,
175 | ),
176 | ),
177 | focusedBorder: OutlineInputBorder(
178 | borderRadius: BorderRadius.circular(16),
179 | borderSide: BorderSide(
180 | color: darkColorScheme.primary,
181 | width: 2,
182 | ),
183 | ),
184 | errorBorder: OutlineInputBorder(
185 | borderRadius: BorderRadius.circular(16),
186 | borderSide: BorderSide(
187 | color: darkColorScheme.error,
188 | width: 1,
189 | ),
190 | ),
191 | focusedErrorBorder: OutlineInputBorder(
192 | borderRadius: BorderRadius.circular(16),
193 | borderSide: BorderSide(
194 | color: darkColorScheme.error,
195 | width: 2,
196 | ),
197 | ),
198 | contentPadding: const EdgeInsets.all(20),
199 | labelStyle: TextStyle(
200 | color: darkColorScheme.onSurfaceVariant,
201 | fontSize: 16,
202 | ),
203 | hintStyle: TextStyle(
204 | color: darkColorScheme.onSurfaceVariant.withValues(alpha: 0.6),
205 | fontSize: 16,
206 | ),
207 | ),
208 |
209 | // Elevated Button Theme
210 | elevatedButtonTheme: ElevatedButtonThemeData(
211 | style: ElevatedButton.styleFrom(
212 | elevation: 0,
213 | backgroundColor: darkColorScheme.primary,
214 | foregroundColor: darkColorScheme.onPrimary,
215 | disabledBackgroundColor: darkColorScheme.onSurface.withValues(alpha: 0.12),
216 | disabledForegroundColor: darkColorScheme.onSurface.withValues(alpha: 0.38),
217 | padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
218 | minimumSize: const Size(0, 56),
219 | shape: RoundedRectangleBorder(
220 | borderRadius: BorderRadius.circular(16),
221 | ),
222 | textStyle: const TextStyle(
223 | fontSize: 16,
224 | fontWeight: FontWeight.w600,
225 | ),
226 | ),
227 | ),
228 |
229 | // Text Button Theme
230 | textButtonTheme: TextButtonThemeData(
231 | style: TextButton.styleFrom(
232 | foregroundColor: darkColorScheme.primary,
233 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
234 | minimumSize: const Size(0, 48),
235 | shape: RoundedRectangleBorder(
236 | borderRadius: BorderRadius.circular(12),
237 | ),
238 | textStyle: const TextStyle(
239 | fontSize: 16,
240 | fontWeight: FontWeight.w600,
241 | ),
242 | ),
243 | ),
244 |
245 | // Card Theme
246 | cardTheme: CardThemeData(
247 | elevation: 0,
248 | color: darkColorScheme.surfaceContainerHigh.withValues(alpha: 0.3),
249 | shape: RoundedRectangleBorder(
250 | borderRadius: BorderRadius.circular(20),
251 | side: BorderSide(
252 | color: darkColorScheme.outline.withValues(alpha: 0.2),
253 | width: 1,
254 | ),
255 | ),
256 | margin: const EdgeInsets.all(8),
257 | ),
258 |
259 | // Scaffold Background
260 | scaffoldBackgroundColor: darkColorScheme.surface,
261 | );
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/lib/domain/authentication/auth_failures.dart:
--------------------------------------------------------------------------------
1 | import "package:freezed_annotation/freezed_annotation.dart";
2 |
3 | part "auth_failures.freezed.dart";
4 |
5 | @freezed
6 | class AuthFailures with _$AuthFailures {
7 | const factory AuthFailures.serverError() = ServerError;
8 |
9 | const factory AuthFailures.emailAlreadyInUse() = EmailAlreadyInUse;
10 |
11 | const factory AuthFailures.invalidEmailAndPasswordCombination() = InvalidEmailAndPasswordCombination;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/domain/authentication/auth_failures.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // coverage:ignore-file
3 | // ignore_for_file: type=lint
4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
5 |
6 | part of 'auth_failures.dart';
7 |
8 | // **************************************************************************
9 | // FreezedGenerator
10 | // **************************************************************************
11 |
12 | // dart format off
13 | T _$identity(T value) => value;
14 |
15 | /// @nodoc
16 | mixin _$AuthFailures {
17 | @override
18 | bool operator ==(Object other) {
19 | return identical(this, other) || (other.runtimeType == runtimeType && other is AuthFailures);
20 | }
21 |
22 | @override
23 | int get hashCode => runtimeType.hashCode;
24 |
25 | @override
26 | String toString() {
27 | return 'AuthFailures()';
28 | }
29 | }
30 |
31 | /// @nodoc
32 | class $AuthFailuresCopyWith<$Res> {
33 | $AuthFailuresCopyWith(AuthFailures _, $Res Function(AuthFailures) __);
34 | }
35 |
36 | /// Adds pattern-matching-related methods to [AuthFailures].
37 | extension AuthFailuresPatterns on AuthFailures {
38 | /// A variant of `map` that fallback to returning `orElse`.
39 | ///
40 | /// It is equivalent to doing:
41 | /// ```dart
42 | /// switch (sealedClass) {
43 | /// case final Subclass value:
44 | /// return ...;
45 | /// case _:
46 | /// return orElse();
47 | /// }
48 | /// ```
49 |
50 | @optionalTypeArgs
51 | TResult maybeMap({
52 | TResult Function(ServerError value)? serverError,
53 | TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse,
54 | TResult Function(InvalidEmailAndPasswordCombination value)? invalidEmailAndPasswordCombination,
55 | required TResult orElse(),
56 | }) {
57 | final _that = this;
58 | switch (_that) {
59 | case ServerError() when serverError != null:
60 | return serverError(_that);
61 | case EmailAlreadyInUse() when emailAlreadyInUse != null:
62 | return emailAlreadyInUse(_that);
63 | case InvalidEmailAndPasswordCombination() when invalidEmailAndPasswordCombination != null:
64 | return invalidEmailAndPasswordCombination(_that);
65 | case _:
66 | return orElse();
67 | }
68 | }
69 |
70 | /// A `switch`-like method, using callbacks.
71 | ///
72 | /// Callbacks receives the raw object, upcasted.
73 | /// It is equivalent to doing:
74 | /// ```dart
75 | /// switch (sealedClass) {
76 | /// case final Subclass value:
77 | /// return ...;
78 | /// case final Subclass2 value:
79 | /// return ...;
80 | /// }
81 | /// ```
82 |
83 | @optionalTypeArgs
84 | TResult map({
85 | required TResult Function(ServerError value) serverError,
86 | required TResult Function(EmailAlreadyInUse value) emailAlreadyInUse,
87 | required TResult Function(InvalidEmailAndPasswordCombination value) invalidEmailAndPasswordCombination,
88 | }) {
89 | final _that = this;
90 | switch (_that) {
91 | case ServerError():
92 | return serverError(_that);
93 | case EmailAlreadyInUse():
94 | return emailAlreadyInUse(_that);
95 | case InvalidEmailAndPasswordCombination():
96 | return invalidEmailAndPasswordCombination(_that);
97 | case _:
98 | throw StateError('Unexpected subclass');
99 | }
100 | }
101 |
102 | /// A variant of `map` that fallback to returning `null`.
103 | ///
104 | /// It is equivalent to doing:
105 | /// ```dart
106 | /// switch (sealedClass) {
107 | /// case final Subclass value:
108 | /// return ...;
109 | /// case _:
110 | /// return null;
111 | /// }
112 | /// ```
113 |
114 | @optionalTypeArgs
115 | TResult? mapOrNull({
116 | TResult? Function(ServerError value)? serverError,
117 | TResult? Function(EmailAlreadyInUse value)? emailAlreadyInUse,
118 | TResult? Function(InvalidEmailAndPasswordCombination value)? invalidEmailAndPasswordCombination,
119 | }) {
120 | final _that = this;
121 | switch (_that) {
122 | case ServerError() when serverError != null:
123 | return serverError(_that);
124 | case EmailAlreadyInUse() when emailAlreadyInUse != null:
125 | return emailAlreadyInUse(_that);
126 | case InvalidEmailAndPasswordCombination() when invalidEmailAndPasswordCombination != null:
127 | return invalidEmailAndPasswordCombination(_that);
128 | case _:
129 | return null;
130 | }
131 | }
132 |
133 | /// A variant of `when` that fallback to an `orElse` callback.
134 | ///
135 | /// It is equivalent to doing:
136 | /// ```dart
137 | /// switch (sealedClass) {
138 | /// case Subclass(:final field):
139 | /// return ...;
140 | /// case _:
141 | /// return orElse();
142 | /// }
143 | /// ```
144 |
145 | @optionalTypeArgs
146 | TResult maybeWhen({
147 | TResult Function()? serverError,
148 | TResult Function()? emailAlreadyInUse,
149 | TResult Function()? invalidEmailAndPasswordCombination,
150 | required TResult orElse(),
151 | }) {
152 | final _that = this;
153 | switch (_that) {
154 | case ServerError() when serverError != null:
155 | return serverError();
156 | case EmailAlreadyInUse() when emailAlreadyInUse != null:
157 | return emailAlreadyInUse();
158 | case InvalidEmailAndPasswordCombination() when invalidEmailAndPasswordCombination != null:
159 | return invalidEmailAndPasswordCombination();
160 | case _:
161 | return orElse();
162 | }
163 | }
164 |
165 | /// A `switch`-like method, using callbacks.
166 | ///
167 | /// As opposed to `map`, this offers destructuring.
168 | /// It is equivalent to doing:
169 | /// ```dart
170 | /// switch (sealedClass) {
171 | /// case Subclass(:final field):
172 | /// return ...;
173 | /// case Subclass2(:final field2):
174 | /// return ...;
175 | /// }
176 | /// ```
177 |
178 | @optionalTypeArgs
179 | TResult when({
180 | required TResult Function() serverError,
181 | required TResult Function() emailAlreadyInUse,
182 | required TResult Function() invalidEmailAndPasswordCombination,
183 | }) {
184 | final _that = this;
185 | switch (_that) {
186 | case ServerError():
187 | return serverError();
188 | case EmailAlreadyInUse():
189 | return emailAlreadyInUse();
190 | case InvalidEmailAndPasswordCombination():
191 | return invalidEmailAndPasswordCombination();
192 | case _:
193 | throw StateError('Unexpected subclass');
194 | }
195 | }
196 |
197 | /// A variant of `when` that fallback to returning `null`
198 | ///
199 | /// It is equivalent to doing:
200 | /// ```dart
201 | /// switch (sealedClass) {
202 | /// case Subclass(:final field):
203 | /// return ...;
204 | /// case _:
205 | /// return null;
206 | /// }
207 | /// ```
208 |
209 | @optionalTypeArgs
210 | TResult? whenOrNull({
211 | TResult? Function()? serverError,
212 | TResult? Function()? emailAlreadyInUse,
213 | TResult? Function()? invalidEmailAndPasswordCombination,
214 | }) {
215 | final _that = this;
216 | switch (_that) {
217 | case ServerError() when serverError != null:
218 | return serverError();
219 | case EmailAlreadyInUse() when emailAlreadyInUse != null:
220 | return emailAlreadyInUse();
221 | case InvalidEmailAndPasswordCombination() when invalidEmailAndPasswordCombination != null:
222 | return invalidEmailAndPasswordCombination();
223 | case _:
224 | return null;
225 | }
226 | }
227 | }
228 |
229 | /// @nodoc
230 |
231 | class ServerError implements AuthFailures {
232 | const ServerError();
233 |
234 | @override
235 | bool operator ==(Object other) {
236 | return identical(this, other) || (other.runtimeType == runtimeType && other is ServerError);
237 | }
238 |
239 | @override
240 | int get hashCode => runtimeType.hashCode;
241 |
242 | @override
243 | String toString() {
244 | return 'AuthFailures.serverError()';
245 | }
246 | }
247 |
248 | /// @nodoc
249 |
250 | class EmailAlreadyInUse implements AuthFailures {
251 | const EmailAlreadyInUse();
252 |
253 | @override
254 | bool operator ==(Object other) {
255 | return identical(this, other) || (other.runtimeType == runtimeType && other is EmailAlreadyInUse);
256 | }
257 |
258 | @override
259 | int get hashCode => runtimeType.hashCode;
260 |
261 | @override
262 | String toString() {
263 | return 'AuthFailures.emailAlreadyInUse()';
264 | }
265 | }
266 |
267 | /// @nodoc
268 |
269 | class InvalidEmailAndPasswordCombination implements AuthFailures {
270 | const InvalidEmailAndPasswordCombination();
271 |
272 | @override
273 | bool operator ==(Object other) {
274 | return identical(this, other) || (other.runtimeType == runtimeType && other is InvalidEmailAndPasswordCombination);
275 | }
276 |
277 | @override
278 | int get hashCode => runtimeType.hashCode;
279 |
280 | @override
281 | String toString() {
282 | return 'AuthFailures.invalidEmailAndPasswordCombination()';
283 | }
284 | }
285 |
286 | // dart format on
287 |
--------------------------------------------------------------------------------
/lib/domain/authentication/auth_value_failures.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import "package:freezed_annotation/freezed_annotation.dart";
3 |
4 | part "auth_value_failures.freezed.dart";
5 |
6 | @freezed
7 | sealed class AuthValueFailures with _$AuthValueFailures {
8 | const factory AuthValueFailures.invalidEmail({required String? failedValue}) = InvalidEmail;
9 |
10 | const factory AuthValueFailures.shortPassword({required String? failedValue}) = ShortPassword;
11 |
12 | const factory AuthValueFailures.noSpecialSymbol({required String? failedValue}) = NoSpecialSymbol;
13 |
14 | const factory AuthValueFailures.noUpperCase({required String? failedValue}) = NoUpperCase;
15 |
16 | const factory AuthValueFailures.noNumber({required String? failedValue}) = NoNumber;
17 | }
18 |
--------------------------------------------------------------------------------
/lib/domain/authentication/auth_value_objects.dart:
--------------------------------------------------------------------------------
1 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart";
2 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_validators.dart";
3 | import "package:firebase_auth_flutter_ddd/domain/core/value_object.dart";
4 | import "package:fpdart/fpdart.dart";
5 |
6 | class EmailAddress extends ValueObject {
7 | factory EmailAddress({String? email}) {
8 | return EmailAddress._(validateEmailAddress(email: email));
9 | }
10 |
11 | const EmailAddress._(this.valueObject);
12 |
13 | @override
14 | final Either, String>? valueObject;
15 | }
16 |
17 | class Password extends ValueObject {
18 | factory Password({String? password}) {
19 | return Password._(validatePassword(password: password));
20 | }
21 |
22 | const Password._(this.valueObject);
23 |
24 | @override
25 | final Either, String>? valueObject;
26 | }
27 |
--------------------------------------------------------------------------------
/lib/domain/authentication/auth_value_validators.dart:
--------------------------------------------------------------------------------
1 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart";
2 | import "package:fpdart/fpdart.dart";
3 |
4 | Either, String> validateEmailAddress({
5 | required String? email,
6 | }) {
7 | // Improved email regex pattern for better validation
8 | const emailRegex =
9 | r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
10 | final emailPattern = RegExp(emailRegex);
11 |
12 | if (email == null || email.trim().isEmpty) {
13 | return left(AuthValueFailures.invalidEmail(failedValue: email ?? ""));
14 | }
15 |
16 | final trimmedEmail = email.trim().toLowerCase();
17 | if (emailPattern.hasMatch(trimmedEmail)) {
18 | return right(trimmedEmail);
19 | } else {
20 | return left(AuthValueFailures.invalidEmail(failedValue: email));
21 | }
22 | }
23 |
24 | Either, String> validatePassword({
25 | required String? password,
26 | }) {
27 | if (password == null || password.isEmpty) {
28 | return left(AuthValueFailures.shortPassword(failedValue: password ?? ""));
29 | }
30 |
31 | final hasMinLength = password.length >= 8; // Changed from > 6 to >= 8 for better security
32 | final hasUppercase = RegExp(r"[A-Z]").hasMatch(password);
33 | final hasDigits = RegExp(r"[0-9]").hasMatch(password);
34 | final hasSpecialCharacters = RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password);
35 |
36 | if (!hasMinLength) {
37 | return left(AuthValueFailures.shortPassword(failedValue: password));
38 | } else if (!hasUppercase) {
39 | return left(AuthValueFailures.noUpperCase(failedValue: password));
40 | } else if (!hasDigits) {
41 | return left(AuthValueFailures.noNumber(failedValue: password));
42 | } else if (!hasSpecialCharacters) {
43 | return left(AuthValueFailures.noSpecialSymbol(failedValue: password));
44 | } else {
45 | return right(password);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/domain/authentication/i_auth_facade.dart:
--------------------------------------------------------------------------------
1 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_failures.dart";
2 | import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_objects.dart";
3 | import "package:fpdart/fpdart.dart";
4 |
5 | abstract class IAuthFacade {
6 | Future> registerWithEmailAndPassword(
7 | {required EmailAddress emailAddress, required Password password});
8 |
9 | Future> signInWithEmailAndPassword(
10 | {required EmailAddress emailAddress, required Password password});
11 |
12 | Future