From 1e7b41b3b854b4bacd224ba4a8fe0f4c2a1f8aa5 Mon Sep 17 00:00:00 2001 From: Vetrichelvan Date: Mon, 21 Jul 2025 17:03:07 +0530 Subject: [PATCH] feat: Set up Firebase authentication and update project structure --- .editorconfig | 17 + .env.example | 1 + .gitignore | 25 + .kiro/settings/mcp.json | 19 + .kiro/steering/dev_workflow.md | 424 +++++++ .kiro/steering/kiro_rules.md | 53 + .kiro/steering/self_improve.md | 72 ++ .kiro/steering/taskmaster.md | 558 +++++++++ .metadata | 30 +- .taskmaster/config.json | 36 + .taskmaster/docs/prd.txt | 59 + .taskmaster/state.json | 6 + .taskmaster/tasks/tasks.json | 99 ++ .taskmaster/templates/example_prd.txt | 47 + AGENTS.md | 438 +++++++ GEMINI.md | 438 +++++++ README.md | 47 +- android/app/build.gradle.kts | 44 + .../firebase_auth_flutter_ddd/MainActivity.kt | 5 + android/build.gradle.kts | 21 + android/settings.gradle.kts | 25 + guides/architecture.md | 70 ++ guides/setup.md | 63 + .../authentication/auth_events.freezed.dart | 799 +++++------- .../authentication/auth_state_controller.dart | 59 +- .../authentication/auth_states.dart | 6 +- .../authentication/auth_states.freezed.dart | 431 +++++-- .../authentication/auth_failures.freezed.dart | 505 +++----- .../authentication/auth_value_failures.dart | 2 +- .../auth_value_failures.freezed.dart | 1094 +++++++---------- .../authentication/auth_value_objects.dart | 6 +- .../authentication/auth_value_validators.dart | 8 +- lib/domain/authentication/i_auth_facade.dart | 4 +- lib/domain/core/errors.dart | 2 +- lib/domain/core/value_object.dart | 2 +- lib/screens/login_page.dart | 192 +-- .../authentication/firebase_auth_facade.dart | 42 +- opencode.json | 26 + pubspec.lock | 394 +++--- pubspec.yaml | 20 +- web/favicon.png | Bin 0 -> 917 bytes web/icons/Icon-192.png | Bin 0 -> 5292 bytes web/icons/Icon-512.png | Bin 0 -> 8252 bytes web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes web/index.html | 38 + web/manifest.json | 35 + 47 files changed, 4260 insertions(+), 2002 deletions(-) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .kiro/settings/mcp.json create mode 100644 .kiro/steering/dev_workflow.md create mode 100644 .kiro/steering/kiro_rules.md create mode 100644 .kiro/steering/self_improve.md create mode 100644 .kiro/steering/taskmaster.md create mode 100644 .taskmaster/config.json create mode 100644 .taskmaster/docs/prd.txt create mode 100644 .taskmaster/state.json create mode 100644 .taskmaster/tasks/tasks.json create mode 100644 .taskmaster/templates/example_prd.txt create mode 100644 AGENTS.md create mode 100644 GEMINI.md create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/main/kotlin/com/example/firebase_auth_flutter_ddd/MainActivity.kt create mode 100644 android/build.gradle.kts create mode 100644 android/settings.gradle.kts create mode 100644 guides/architecture.md create mode 100644 guides/setup.md create mode 100644 opencode.json create mode 100644 web/favicon.png create mode 100644 web/icons/Icon-192.png create mode 100644 web/icons/Icon-512.png create mode 100644 web/icons/Icon-maskable-192.png create mode 100644 web/icons/Icon-maskable-512.png create mode 100644 web/index.html create mode 100644 web/manifest.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..82d3a60 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = tab +tab_width = 4 +indent_size = 4 +end_of_line = lf +max_line_length = 120 +ij_visual_guides = 120 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,py,html}] +charset = utf-8 + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6255e4e --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +OLLAMA_API_KEY="ollama" diff --git a/.gitignore b/.gitignore index 579907c..0560aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,28 @@ app.*.map.json .dart_tool ios/firebase_app_id_file.json + +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +dev-debug.log +# Dependency directories +node_modules/ +# Environment variables +.env +# Editor directories and files +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# OS specific + +# Task files +# tasks.json +# tasks/ + +.gemini \ No newline at end of file diff --git a/.kiro/settings/mcp.json b/.kiro/settings/mcp.json new file mode 100644 index 0000000..b157908 --- /dev/null +++ b/.kiro/settings/mcp.json @@ -0,0 +1,19 @@ +{ + "mcpServers": { + "task-master-ai": { + "command": "npx", + "args": ["-y", "--package=task-master-ai", "task-master-ai"], + "env": { + "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", + "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", + "OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE", + "GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE", + "XAI_API_KEY": "YOUR_XAI_KEY_HERE", + "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", + "MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE", + "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", + "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE" + } + } + } +} diff --git a/.kiro/steering/dev_workflow.md b/.kiro/steering/dev_workflow.md new file mode 100644 index 0000000..0d102dd --- /dev/null +++ b/.kiro/steering/dev_workflow.md @@ -0,0 +1,424 @@ +--- +description: Guide for using Taskmaster to manage task-driven development workflows +globs: **/* +alwaysApply: true +--- + +# Taskmaster Development Workflow + +This guide outlines the standard process for using Taskmaster to manage software development projects. It is written as a set of instructions for you, the AI agent. + +- **Your Default Stance**: For most projects, the user can work directly within the `master` task context. Your initial actions should operate on this default context unless a clear pattern for multi-context work emerges. +- **Your Goal**: Your role is to elevate the user's workflow by intelligently introducing advanced features like **Tagged Task Lists** when you detect the appropriate context. Do not force tags on the user; suggest them as a helpful solution to a specific need. + +## The Basic Loop +The fundamental development cycle you will facilitate is: +1. **`list`**: Show the user what needs to be done. +2. **`next`**: Help the user decide what to work on. +3. **`show `**: Provide details for a specific task. +4. **`expand `**: Break down a complex task into smaller, manageable subtasks. +5. **Implement**: The user writes the code and tests. +6. **`update-subtask`**: Log progress and findings on behalf of the user. +7. **`set-status`**: Mark tasks and subtasks as `done` as work is completed. +8. **Repeat**. + +All your standard command executions should operate on the user's current task context, which defaults to `master`. + +--- + +## Standard Development Workflow Process + +### Simple Workflow (Default Starting Point) + +For new projects or when users are getting started, operate within the `master` tag context: + +- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input=''` (see @`taskmaster.md`) to generate initial tasks.json with tagged structure +- Configure rule sets during initialization with `--rules` flag (e.g., `task-master init --rules kiro,windsurf`) or manage them later with `task-master rules add/remove` commands +- Begin coding sessions with `get_tasks` / `task-master list` (see @`taskmaster.md`) to see current tasks, status, and IDs +- Determine the next task to work on using `next_task` / `task-master next` (see @`taskmaster.md`) +- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.md`) before breaking down tasks +- Review complexity report using `complexity_report` / `task-master complexity-report` (see @`taskmaster.md`) +- Select tasks based on dependencies (all marked 'done'), priority level, and ID order +- View specific task details using `get_task` / `task-master show ` (see @`taskmaster.md`) to understand implementation requirements +- Break down complex tasks using `expand_task` / `task-master expand --id= --force --research` (see @`taskmaster.md`) with appropriate flags like `--force` (to replace existing subtasks) and `--research` +- Implement code following task details, dependencies, and project standards +- Mark completed tasks with `set_task_status` / `task-master set-status --id= --status=done` (see @`taskmaster.md`) +- Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from= --prompt="..."` or `update_task` / `task-master update-task --id= --prompt="..."` (see @`taskmaster.md`) + +--- + +## Leveling Up: Agent-Led Multi-Context Workflows + +While the basic workflow is powerful, your primary opportunity to add value is by identifying when to introduce **Tagged Task Lists**. These patterns are your tools for creating a more organized and efficient development environment for the user, especially if you detect agentic or parallel development happening across the same session. + +**Critical Principle**: Most users should never see a difference in their experience. Only introduce advanced workflows when you detect clear indicators that the project has evolved beyond simple task management. + +### When to Introduce Tags: Your Decision Patterns + +Here are the patterns to look for. When you detect one, you should propose the corresponding workflow to the user. + +#### Pattern 1: Simple Git Feature Branching +This is the most common and direct use case for tags. + +- **Trigger**: The user creates a new git branch (e.g., `git checkout -b feature/user-auth`). +- **Your Action**: Propose creating a new tag that mirrors the branch name to isolate the feature's tasks from `master`. +- **Your Suggested Prompt**: *"I see you've created a new branch named 'feature/user-auth'. To keep all related tasks neatly organized and separate from your main list, I can create a corresponding task tag for you. This helps prevent merge conflicts in your `tasks.json` file later. Shall I create the 'feature-user-auth' tag?"* +- **Tool to Use**: `task-master add-tag --from-branch` + +#### Pattern 2: Team Collaboration +- **Trigger**: The user mentions working with teammates (e.g., "My teammate Alice is handling the database schema," or "I need to review Bob's work on the API."). +- **Your Action**: Suggest creating a separate tag for the user's work to prevent conflicts with shared master context. +- **Your Suggested Prompt**: *"Since you're working with Alice, I can create a separate task context for your work to avoid conflicts. This way, Alice can continue working with the master list while you have your own isolated context. When you're ready to merge your work, we can coordinate the tasks back to master. Shall I create a tag for your current work?"* +- **Tool to Use**: `task-master add-tag my-work --copy-from-current --description="My tasks while collaborating with Alice"` + +#### Pattern 3: Experiments or Risky Refactors +- **Trigger**: The user wants to try something that might not be kept (e.g., "I want to experiment with switching our state management library," or "Let's refactor the old API module, but I want to keep the current tasks as a reference."). +- **Your Action**: Propose creating a sandboxed tag for the experimental work. +- **Your Suggested Prompt**: *"This sounds like a great experiment. To keep these new tasks separate from our main plan, I can create a temporary 'experiment-zustand' tag for this work. If we decide not to proceed, we can simply delete the tag without affecting the main task list. Sound good?"* +- **Tool to Use**: `task-master add-tag experiment-zustand --description="Exploring Zustand migration"` + +#### Pattern 4: Large Feature Initiatives (PRD-Driven) +This is a more structured approach for significant new features or epics. + +- **Trigger**: The user describes a large, multi-step feature that would benefit from a formal plan. +- **Your Action**: Propose a comprehensive, PRD-driven workflow. +- **Your Suggested Prompt**: *"This sounds like a significant new feature. To manage this effectively, I suggest we create a dedicated task context for it. Here's the plan: I'll create a new tag called 'feature-xyz', then we can draft a Product Requirements Document (PRD) together to scope the work. Once the PRD is ready, I'll automatically generate all the necessary tasks within that new tag. How does that sound?"* +- **Your Implementation Flow**: + 1. **Create an empty tag**: `task-master add-tag feature-xyz --description "Tasks for the new XYZ feature"`. You can also start by creating a git branch if applicable, and then create the tag from that branch. + 2. **Collaborate & Create PRD**: Work with the user to create a detailed PRD file (e.g., `.taskmaster/docs/feature-xyz-prd.txt`). + 3. **Parse PRD into the new tag**: `task-master parse-prd .taskmaster/docs/feature-xyz-prd.txt --tag feature-xyz` + 4. **Prepare the new task list**: Follow up by suggesting `analyze-complexity` and `expand-all` for the newly created tasks within the `feature-xyz` tag. + +#### Pattern 5: Version-Based Development +Tailor your approach based on the project maturity indicated by tag names. + +- **Prototype/MVP Tags** (`prototype`, `mvp`, `poc`, `v0.x`): + - **Your Approach**: Focus on speed and functionality over perfection + - **Task Generation**: Create tasks that emphasize "get it working" over "get it perfect" + - **Complexity Level**: Lower complexity, fewer subtasks, more direct implementation paths + - **Research Prompts**: Include context like "This is a prototype - prioritize speed and basic functionality over optimization" + - **Example Prompt Addition**: *"Since this is for the MVP, I'll focus on tasks that get core functionality working quickly rather than over-engineering."* + +- **Production/Mature Tags** (`v1.0+`, `production`, `stable`): + - **Your Approach**: Emphasize robustness, testing, and maintainability + - **Task Generation**: Include comprehensive error handling, testing, documentation, and optimization + - **Complexity Level**: Higher complexity, more detailed subtasks, thorough implementation paths + - **Research Prompts**: Include context like "This is for production - prioritize reliability, performance, and maintainability" + - **Example Prompt Addition**: *"Since this is for production, I'll ensure tasks include proper error handling, testing, and documentation."* + +### Advanced Workflow (Tag-Based & PRD-Driven) + +**When to Transition**: Recognize when the project has evolved (or has initiated a project which existing code) beyond simple task management. Look for these indicators: +- User mentions teammates or collaboration needs +- Project has grown to 15+ tasks with mixed priorities +- User creates feature branches or mentions major initiatives +- User initializes Taskmaster on an existing, complex codebase +- User describes large features that would benefit from dedicated planning + +**Your Role in Transition**: Guide the user to a more sophisticated workflow that leverages tags for organization and PRDs for comprehensive planning. + +#### Master List Strategy (High-Value Focus) +Once you transition to tag-based workflows, the `master` tag should ideally contain only: +- **High-level deliverables** that provide significant business value +- **Major milestones** and epic-level features +- **Critical infrastructure** work that affects the entire project +- **Release-blocking** items + +**What NOT to put in master**: +- Detailed implementation subtasks (these go in feature-specific tags' parent tasks) +- Refactoring work (create dedicated tags like `refactor-auth`) +- Experimental features (use `experiment-*` tags) +- Team member-specific tasks (use person-specific tags) + +#### PRD-Driven Feature Development + +**For New Major Features**: +1. **Identify the Initiative**: When user describes a significant feature +2. **Create Dedicated Tag**: `add_tag feature-[name] --description="[Feature description]"` +3. **Collaborative PRD Creation**: Work with user to create comprehensive PRD in `.taskmaster/docs/feature-[name]-prd.txt` +4. **Parse & Prepare**: + - `parse_prd .taskmaster/docs/feature-[name]-prd.txt --tag=feature-[name]` + - `analyze_project_complexity --tag=feature-[name] --research` + - `expand_all --tag=feature-[name] --research` +5. **Add Master Reference**: Create a high-level task in `master` that references the feature tag + +**For Existing Codebase Analysis**: +When users initialize Taskmaster on existing projects: +1. **Codebase Discovery**: Use your native tools for producing deep context about the code base. You may use `research` tool with `--tree` and `--files` to collect up to date information using the existing architecture as context. +2. **Collaborative Assessment**: Work with user to identify improvement areas, technical debt, or new features +3. **Strategic PRD Creation**: Co-author PRDs that include: + - Current state analysis (based on your codebase research) + - Proposed improvements or new features + - Implementation strategy considering existing code +4. **Tag-Based Organization**: Parse PRDs into appropriate tags (`refactor-api`, `feature-dashboard`, `tech-debt`, etc.) +5. **Master List Curation**: Keep only the most valuable initiatives in master + +The parse-prd's `--append` flag enables the user to parse multiple PRDs within tags or across tags. PRDs should be focused and the number of tasks they are parsed into should be strategically chosen relative to the PRD's complexity and level of detail. + +### Workflow Transition Examples + +**Example 1: Simple → Team-Based** +``` +User: "Alice is going to help with the API work" +Your Response: "Great! To avoid conflicts, I'll create a separate task context for your work. Alice can continue with the master list while you work in your own context. When you're ready to merge, we can coordinate the tasks back together." +Action: add_tag my-api-work --copy-from-current --description="My API tasks while collaborating with Alice" +``` + +**Example 2: Simple → PRD-Driven** +``` +User: "I want to add a complete user dashboard with analytics, user management, and reporting" +Your Response: "This sounds like a major feature that would benefit from detailed planning. Let me create a dedicated context for this work and we can draft a PRD together to ensure we capture all requirements." +Actions: +1. add_tag feature-dashboard --description="User dashboard with analytics and management" +2. Collaborate on PRD creation +3. parse_prd dashboard-prd.txt --tag=feature-dashboard +4. Add high-level "User Dashboard" task to master +``` + +**Example 3: Existing Project → Strategic Planning** +``` +User: "I just initialized Taskmaster on my existing React app. It's getting messy and I want to improve it." +Your Response: "Let me research your codebase to understand the current architecture, then we can create a strategic plan for improvements." +Actions: +1. research "Current React app architecture and improvement opportunities" --tree --files=src/ +2. Collaborate on improvement PRD based on findings +3. Create tags for different improvement areas (refactor-components, improve-state-management, etc.) +4. Keep only major improvement initiatives in master +``` + +--- + +## Primary Interaction: MCP Server vs. CLI + +Taskmaster offers two primary ways to interact: + +1. **MCP Server (Recommended for Integrated Tools)**: + - For AI agents and integrated development environments (like Kiro), interacting via the **MCP server is the preferred method**. + - The MCP server exposes Taskmaster functionality through a set of tools (e.g., `get_tasks`, `add_subtask`). + - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing. + - Refer to @`mcp.md` for details on the MCP architecture and available tools. + - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in @`taskmaster.md`. + - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change. + - **Note**: MCP tools fully support tagged task lists with complete tag management capabilities. + +2. **`task-master` CLI (For Users & Fallback)**: + - The global `task-master` command provides a user-friendly interface for direct terminal interaction. + - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP. + - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`. + - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`). + - Refer to @`taskmaster.md` for a detailed command reference. + - **Tagged Task Lists**: CLI fully supports the new tagged system with seamless migration. + +## How the Tag System Works (For Your Reference) + +- **Data Structure**: Tasks are organized into separate contexts (tags) like "master", "feature-branch", or "v2.0". +- **Silent Migration**: Existing projects automatically migrate to use a "master" tag with zero disruption. +- **Context Isolation**: Tasks in different tags are completely separate. Changes in one tag do not affect any other tag. +- **Manual Control**: The user is always in control. There is no automatic switching. You facilitate switching by using `use-tag `. +- **Full CLI & MCP Support**: All tag management commands are available through both the CLI and MCP tools for you to use. Refer to @`taskmaster.md` for a full command list. + +--- + +## Task Complexity Analysis + +- Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.md`) for comprehensive analysis +- Review complexity report via `complexity_report` / `task-master complexity-report` (see @`taskmaster.md`) for a formatted, readable version. +- Focus on tasks with highest complexity scores (8-10) for detailed breakdown +- Use analysis results to determine appropriate subtask allocation +- Note that reports are automatically used by the `expand_task` tool/command + +## Task Breakdown Process + +- Use `expand_task` / `task-master expand --id=`. It automatically uses the complexity report if found, otherwise generates default number of subtasks. +- Use `--num=` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations. +- Add `--research` flag to leverage Perplexity AI for research-backed expansion. +- Add `--force` flag to clear existing subtasks before generating new ones (default is to append). +- Use `--prompt=""` to provide additional context when needed. +- Review and adjust generated subtasks as necessary. +- Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`. +- If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=`. + +## Implementation Drift Handling + +- When implementation differs significantly from planned approach +- When future tasks need modification due to current implementation choices +- When new dependencies or requirements emerge +- Use `update` / `task-master update --from= --prompt='\nUpdate context...' --research` to update multiple future tasks. +- Use `update_task` / `task-master update-task --id= --prompt='\nUpdate context...' --research` to update a single specific task. + +## Task Status Management + +- Use 'pending' for tasks ready to be worked on +- Use 'done' for completed and verified tasks +- Use 'deferred' for postponed tasks +- Add custom status values as needed for project-specific workflows + +## Task Structure Fields + +- **id**: Unique identifier for the task (Example: `1`, `1.1`) +- **title**: Brief, descriptive title (Example: `"Initialize Repo"`) +- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) +- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) +- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work +- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) +- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) +- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) +- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) +- Refer to task structure details (previously linked to `tasks.md`). + +## Configuration Management (Updated) + +Taskmaster configuration is managed through two main mechanisms: + +1. **`.taskmaster/config.json` File (Primary):** + * Located in the project root directory. + * Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc. + * **Tagged System Settings**: Includes `global.defaultTag` (defaults to "master") and `tags` section for tag management configuration. + * **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing. + * **View/Set specific models via `task-master models` command or `models` MCP tool.** + * Created automatically when you run `task-master models --setup` for the first time or during tagged system migration. + +2. **Environment Variables (`.env` / `mcp.json`):** + * Used **only** for sensitive API keys and specific endpoint URLs. + * Place API keys (one per provider) in a `.env` file in the project root for CLI usage. + * For MCP/Kiro integration, configure these keys in the `env` section of `.kiro/mcp.json`. + * Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.md`). + +3. **`.taskmaster/state.json` File (Tagged System State):** + * Tracks current tag context and migration status. + * Automatically created during tagged system migration. + * Contains: `currentTag`, `lastSwitched`, `migrationNoticeShown`. + +**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool. +**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.kiro/mcp.json`. +**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project. + +## Rules Management + +Taskmaster supports multiple AI coding assistant rule sets that can be configured during project initialization or managed afterward: + +- **Available Profiles**: Claude Code, Cline, Codex, Kiro, Roo Code, Trae, Windsurf (claude, cline, codex, kiro, roo, trae, windsurf) +- **During Initialization**: Use `task-master init --rules kiro,windsurf` to specify which rule sets to include +- **After Initialization**: Use `task-master rules add ` or `task-master rules remove ` to manage rule sets +- **Interactive Setup**: Use `task-master rules setup` to launch an interactive prompt for selecting rule profiles +- **Default Behavior**: If no `--rules` flag is specified during initialization, all available rule profiles are included +- **Rule Structure**: Each profile creates its own directory (e.g., `.kiro/steering`, `.roo/rules`) with appropriate configuration files + +## Determining the Next Task + +- Run `next_task` / `task-master next` to show the next task to work on. +- The command identifies tasks with all dependencies satisfied +- Tasks are prioritized by priority level, dependency count, and ID +- The command shows comprehensive task information including: + - Basic task details and description + - Implementation details + - Subtasks (if they exist) + - Contextual suggested actions +- Recommended before starting any new development work +- Respects your project's dependency structure +- Ensures tasks are completed in the appropriate sequence +- Provides ready-to-use commands for common task actions + +## Viewing Specific Task Details + +- Run `get_task` / `task-master show ` to view a specific task. +- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) +- Displays comprehensive information similar to the next command, but for a specific task +- For parent tasks, shows all subtasks and their current status +- For subtasks, shows parent task information and relationship +- Provides contextual suggested actions appropriate for the specific task +- Useful for examining task details before implementation or checking status + +## Managing Task Dependencies + +- Use `add_dependency` / `task-master add-dependency --id= --depends-on=` to add a dependency. +- Use `remove_dependency` / `task-master remove-dependency --id= --depends-on=` to remove a dependency. +- The system prevents circular dependencies and duplicate dependency entries +- Dependencies are checked for existence before being added or removed +- Task files are automatically regenerated after dependency changes +- Dependencies are visualized with status indicators in task listings and files + +## Task Reorganization + +- Use `move_task` / `task-master move --from= --to=` to move tasks or subtasks within the hierarchy +- This command supports several use cases: + - Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`) + - Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`) + - Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`) + - Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`) + - Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`) + - Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`) +- The system includes validation to prevent data loss: + - Allows moving to non-existent IDs by creating placeholder tasks + - Prevents moving to existing task IDs that have content (to avoid overwriting) + - Validates source tasks exist before attempting to move them +- The system maintains proper parent-child relationships and dependency integrity +- Task files are automatically regenerated after the move operation +- This provides greater flexibility in organizing and refining your task structure as project understanding evolves +- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs. + +## Iterative Subtask Implementation + +Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: + +1. **Understand the Goal (Preparation):** + * Use `get_task` / `task-master show ` (see @`taskmaster.md`) to thoroughly understand the specific goals and requirements of the subtask. + +2. **Initial Exploration & Planning (Iteration 1):** + * This is the first attempt at creating a concrete implementation plan. + * Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification. + * Determine the intended code changes (diffs) and their locations. + * Gather *all* relevant details from this exploration phase. + +3. **Log the Plan:** + * Run `update_subtask` / `task-master update-subtask --id= --prompt=''`. + * Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`. + +4. **Verify the Plan:** + * Run `get_task` / `task-master show ` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details. + +5. **Begin Implementation:** + * Set the subtask status using `set_task_status` / `task-master set-status --id= --status=in-progress`. + * Start coding based on the logged plan. + +6. **Refine and Log Progress (Iteration 2+):** + * As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches. + * **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy. + * **Regularly** use `update_subtask` / `task-master update-subtask --id= --prompt='\n- What worked...\n- What didn't work...'` to append new findings. + * **Crucially, log:** + * What worked ("fundamental truths" discovered). + * What didn't work and why (to avoid repeating mistakes). + * Specific code snippets or configurations that were successful. + * Decisions made, especially if confirmed with user input. + * Any deviations from the initial plan and the reasoning. + * The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors. + +7. **Review & Update Rules (Post-Implementation):** + * Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history. + * Identify any new or modified code patterns, conventions, or best practices established during the implementation. + * Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.md` and `self_improve.md`). + +8. **Mark Task Complete:** + * After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id= --status=done`. + +9. **Commit Changes (If using Git):** + * Stage the relevant code changes and any updated/new rule files (`git add .`). + * Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments. + * Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask \n\n- Details about changes...\n- Updated rule Y for pattern Z'`). + * Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.md`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one. + +10. **Proceed to Next Subtask:** + * Identify the next subtask (e.g., using `next_task` / `task-master next`). + +## Code Analysis & Refactoring Techniques + +- **Top-Level Function Search**: + - Useful for understanding module structure or planning refactors. + - Use grep/ripgrep to find exported functions/constants: + `rg "export (async function|function|const) \w+"` or similar patterns. + - Can help compare functions between files during migrations or identify potential naming conflicts. + +--- +*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.* \ No newline at end of file diff --git a/.kiro/steering/kiro_rules.md b/.kiro/steering/kiro_rules.md new file mode 100644 index 0000000..c8254e1 --- /dev/null +++ b/.kiro/steering/kiro_rules.md @@ -0,0 +1,53 @@ +--- +description: Guidelines for creating and maintaining Kiro rules to ensure consistency and effectiveness. +globs: .kiro/steering/*.md +alwaysApply: true +--- + +- **Required Rule Structure:** + ```markdown + --- + description: Clear, one-line description of what the rule enforces + globs: path/to/files/*.ext, other/path/**/* + alwaysApply: boolean + --- + + - **Main Points in Bold** + - Sub-points with details + - Examples and explanations + ``` + +- **File References:** + - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files + - Example: [prisma.md](.kiro/steering/prisma.md) for rule references + - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references + +- **Code Examples:** + - Use language-specific code blocks + ```typescript + // ✅ DO: Show good examples + const goodExample = true; + + // ❌ DON'T: Show anti-patterns + const badExample = false; + ``` + +- **Rule Content Guidelines:** + - Start with high-level overview + - Include specific, actionable requirements + - Show examples of correct implementation + - Reference existing code when possible + - Keep rules DRY by referencing other rules + +- **Rule Maintenance:** + - Update rules when new patterns emerge + - Add examples from actual codebase + - Remove outdated patterns + - Cross-reference related rules + +- **Best Practices:** + - Use bullet points for clarity + - Keep descriptions concise + - Include both DO and DON'T examples + - Reference actual code over theoretical examples + - Use consistent formatting across rules \ No newline at end of file diff --git a/.kiro/steering/self_improve.md b/.kiro/steering/self_improve.md new file mode 100644 index 0000000..c1cd97f --- /dev/null +++ b/.kiro/steering/self_improve.md @@ -0,0 +1,72 @@ +--- +description: Guidelines for continuously improving Kiro rules based on emerging code patterns and best practices. +globs: **/* +alwaysApply: true +--- + +- **Rule Improvement Triggers:** + - New code patterns not covered by existing rules + - Repeated similar implementations across files + - Common error patterns that could be prevented + - New libraries or tools being used consistently + - Emerging best practices in the codebase + +- **Analysis Process:** + - Compare new code with existing rules + - Identify patterns that should be standardized + - Look for references to external documentation + - Check for consistent error handling patterns + - Monitor test patterns and coverage + +- **Rule Updates:** + - **Add New Rules When:** + - A new technology/pattern is used in 3+ files + - Common bugs could be prevented by a rule + - Code reviews repeatedly mention the same feedback + - New security or performance patterns emerge + + - **Modify Existing Rules When:** + - Better examples exist in the codebase + - Additional edge cases are discovered + - Related rules have been updated + - Implementation details have changed + +- **Example Pattern Recognition:** + ```typescript + // If you see repeated patterns like: + const data = await prisma.user.findMany({ + select: { id: true, email: true }, + where: { status: 'ACTIVE' } + }); + + // Consider adding to [prisma.md](.kiro/steering/prisma.md): + // - Standard select fields + // - Common where conditions + // - Performance optimization patterns + ``` + +- **Rule Quality Checks:** + - Rules should be actionable and specific + - Examples should come from actual code + - References should be up to date + - Patterns should be consistently enforced + +- **Continuous Improvement:** + - Monitor code review comments + - Track common development questions + - Update rules after major refactors + - Add links to relevant documentation + - Cross-reference related rules + +- **Rule Deprecation:** + - Mark outdated patterns as deprecated + - Remove rules that no longer apply + - Update references to deprecated rules + - Document migration paths for old patterns + +- **Documentation Updates:** + - Keep examples synchronized with code + - Update references to external docs + - Maintain links between related rules + - Document breaking changes +Follow [kiro_rules.md](.kiro/steering/kiro_rules.md) for proper rule formatting and structure. diff --git a/.kiro/steering/taskmaster.md b/.kiro/steering/taskmaster.md new file mode 100644 index 0000000..5a01614 --- /dev/null +++ b/.kiro/steering/taskmaster.md @@ -0,0 +1,558 @@ +--- +description: Comprehensive reference for Taskmaster MCP tools and CLI commands. +globs: **/* +alwaysApply: true +--- + +# Taskmaster Tool & Command Reference + +This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Kiro, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback. + +**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback. + +**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`. + +**🏷️ Tagged Task Lists System:** Task Master now supports **tagged task lists** for multi-context task management. This allows you to maintain separate, isolated lists of tasks for different features, branches, or experiments. Existing projects are seamlessly migrated to use a default "master" tag. Most commands now support a `--tag ` flag to specify which context to operate on. If omitted, commands use the currently active tag. + +--- + +## Initialization & Setup + +### 1. Initialize Project (`init`) + +* **MCP Tool:** `initialize_project` +* **CLI Command:** `task-master init [options]` +* **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.` +* **Key CLI Options:** + * `--name `: `Set the name for your project in Taskmaster's configuration.` + * `--description `: `Provide a brief description for your project.` + * `--version `: `Set the initial version for your project, e.g., '0.1.0'.` + * `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.` +* **Usage:** Run this once at the beginning of a new project. +* **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.` +* **Key MCP Parameters/Options:** + * `projectName`: `Set the name for your project.` (CLI: `--name `) + * `projectDescription`: `Provide a brief description for your project.` (CLI: `--description `) + * `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version `) + * `authorName`: `Author name.` (CLI: `--author `) + * `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`) + * `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`) + * `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`) +* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Kiro. Operates on the current working directory of the MCP server. +* **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in .taskmaster/templates/example_prd.txt. +* **Tagging:** Use the `--tag` option to parse the PRD into a specific, non-default tag context. If the tag doesn't exist, it will be created automatically. Example: `task-master parse-prd spec.txt --tag=new-feature`. + +### 2. Parse PRD (`parse_prd`) + +* **MCP Tool:** `parse_prd` +* **CLI Command:** `task-master parse-prd [file] [options]` +* **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` +* **Key Parameters/Options:** + * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input `) + * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output `) + * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks `) + * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) +* **Usage:** Useful for bootstrapping a project from an existing requirements document. +* **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `.taskmaster/templates/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`. + +--- + +## AI Model Configuration + +### 2. Manage Models (`models`) +* **MCP Tool:** `models` +* **CLI Command:** `task-master models [options]` +* **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.` +* **Key MCP Parameters/Options:** + * `setMain `: `Set the primary model ID for task generation/updates.` (CLI: `--set-main `) + * `setResearch `: `Set the model ID for research-backed operations.` (CLI: `--set-research `) + * `setFallback `: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback `) + * `ollama `: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`) + * `openrouter `: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`) + * `listAvailableModels `: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically) + * `projectRoot `: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically) +* **Key CLI Options:** + * `--set-main `: `Set the primary model.` + * `--set-research `: `Set the research model.` + * `--set-fallback `: `Set the fallback model.` + * `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).` + * `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.` + * `--bedrock`: `Specify that the provided model ID is for AWS Bedrock (use with --set-*).` + * `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.` +* **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`. +* **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-=` along with either `--ollama` or `--openrouter`. +* **Notes:** Configuration is stored in `.taskmaster/config.json` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live. +* **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them. +* **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80. +* **Warning:** DO NOT MANUALLY EDIT THE .taskmaster/config.json FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback. + +--- + +## Task Listing & Viewing + +### 3. Get Tasks (`get_tasks`) + +* **MCP Tool:** `get_tasks` +* **CLI Command:** `task-master list [options]` +* **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.` +* **Key Parameters/Options:** + * `status`: `Show only Taskmaster tasks matching this status (or multiple statuses, comma-separated), e.g., 'pending' or 'done,in-progress'.` (CLI: `-s, --status `) + * `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`) + * `tag`: `Specify which tag context to list tasks from. Defaults to the current active tag.` (CLI: `--tag `) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Get an overview of the project status, often used at the start of a work session. + +### 4. Get Next Task (`next_task`) + +* **MCP Tool:** `next_task` +* **CLI Command:** `task-master next [options]` +* **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) + * `tag`: `Specify which tag context to use. Defaults to the current active tag.` (CLI: `--tag `) +* **Usage:** Identify what to work on next according to the plan. + +### 5. Get Task Details (`get_task`) + +* **MCP Tool:** `get_task` +* **CLI Command:** `task-master show [id] [options]` +* **Description:** `Display detailed information for one or more specific Taskmaster tasks or subtasks by ID.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task (e.g., '15'), subtask (e.g., '15.2'), or a comma-separated list of IDs ('1,5,10.2') you want to view.` (CLI: `[id]` positional or `-i, --id `) + * `tag`: `Specify which tag context to get the task(s) from. Defaults to the current active tag.` (CLI: `--tag `) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Understand the full details for a specific task. When multiple IDs are provided, a summary table is shown. +* **CRITICAL INFORMATION** If you need to collect information from multiple tasks, use comma-separated IDs (i.e. 1,2,3) to receive an array of tasks. Do not needlessly get tasks one at a time if you need to get many as that is wasteful. + +--- + +## Task Creation & Modification + +### 6. Add Task (`add_task`) + +* **MCP Tool:** `add_task` +* **CLI Command:** `task-master add-task [options]` +* **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.` +* **Key Parameters/Options:** + * `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt `) + * `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies `) + * `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority `) + * `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`) + * `tag`: `Specify which tag context to add the task to. Defaults to the current active tag.` (CLI: `--tag `) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Quickly add newly identified tasks during development. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 7. Add Subtask (`add_subtask`) + +* **MCP Tool:** `add_subtask` +* **CLI Command:** `task-master add-subtask [options]` +* **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.` +* **Key Parameters/Options:** + * `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent `) + * `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id `) + * `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title `) + * `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`) + * `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`) + * `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`) + * `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after adding the subtask.` (CLI: `--skip-generate`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Break down tasks manually or reorganize existing tasks. + +### 8. Update Tasks (`update`) + +* **MCP Tool:** `update` +* **CLI Command:** `task-master update [options]` +* **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.` +* **Key Parameters/Options:** + * `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`) + * `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'` +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 9. Update Task (`update_task`) + +* **MCP Tool:** `update_task` +* **CLI Command:** `task-master update-task [options]` +* **Description:** `Modify a specific Taskmaster task by ID, incorporating new information or changes. By default, this replaces the existing task details.` +* **Key Parameters/Options:** + * `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', you want to update.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`) + * `append`: `If true, appends the prompt content to the task's details with a timestamp, rather than replacing them. Behaves like update-subtask.` (CLI: `--append`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Refine a specific task based on new understanding. Use `--append` to log progress without creating subtasks. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 10. Update Subtask (`update_subtask`) + +* **MCP Tool:** `update_subtask` +* **CLI Command:** `task-master update-subtask [options]` +* **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster subtask, e.g., '5.2', to update with new information.` (CLI: `-i, --id <id>`) + * `prompt`: `Required. The information, findings, or progress notes to append to the subtask's details with a timestamp.` (CLI: `-p, --prompt <text>`) + * `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`) + * `tag`: `Specify which tag context the subtask belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Log implementation progress, findings, and discoveries during subtask development. Each update is timestamped and appended to preserve the implementation journey. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 11. Set Task Status (`set_task_status`) + +* **MCP Tool:** `set_task_status` +* **CLI Command:** `task-master set-status [options]` +* **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`) + * `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Mark progress as tasks move through the development cycle. + +### 12. Remove Task (`remove_task`) + +* **MCP Tool:** `remove_task` +* **CLI Command:** `task-master remove-task [options]` +* **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`) + * `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project. +* **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks. + +--- + +## Task Structure & Breakdown + +### 13. Expand Task (`expand_task`) + +* **MCP Tool:** `expand_task` +* **CLI Command:** `task-master expand [options]` +* **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.` +* **Key Parameters/Options:** + * `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`) + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`) + * `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`) + * `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 14. Expand All Tasks (`expand_all`) + +* **MCP Tool:** `expand_all` +* **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag) +* **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.` +* **Key Parameters/Options:** + * `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`) + * `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`) + * `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`) + * `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`) + * `tag`: `Specify which tag context to expand. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 15. Clear Subtasks (`clear_subtasks`) + +* **MCP Tool:** `clear_subtasks` +* **CLI Command:** `task-master clear-subtasks [options]` +* **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.` +* **Key Parameters/Options:** + * `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using 'all'.` (CLI: `-i, --id <ids>`) + * `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement. + +### 16. Remove Subtask (`remove_subtask`) + +* **MCP Tool:** `remove_subtask` +* **CLI Command:** `task-master remove-subtask [options]` +* **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.` +* **Key Parameters/Options:** + * `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`) + * `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`) + * `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after removing the subtask.` (CLI: `--skip-generate`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task. + +### 17. Move Task (`move_task`) + +* **MCP Tool:** `move_task` +* **CLI Command:** `task-master move [options]` +* **Description:** `Move a task or subtask to a new position within the task hierarchy.` +* **Key Parameters/Options:** + * `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`) + * `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like: + * Moving a task to become a subtask + * Moving a subtask to become a standalone task + * Moving a subtask to a different parent + * Reordering subtasks within the same parent + * Moving a task to a new, non-existent ID (automatically creates placeholders) + * Moving multiple tasks at once with comma-separated IDs +* **Validation Features:** + * Allows moving tasks to non-existent destination IDs (creates placeholder tasks) + * Prevents moving to existing task IDs that already have content (to avoid overwriting) + * Validates that source tasks exist before attempting to move them + * Maintains proper parent-child relationships +* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3. +* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions. +* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches. + +--- + +## Dependency Management + +### 18. Add Dependency (`add_dependency`) + +* **MCP Tool:** `add_dependency` +* **CLI Command:** `task-master add-dependency [options]` +* **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`) +* **Usage:** Establish the correct order of execution between tasks. + +### 19. Remove Dependency (`remove_dependency`) + +* **MCP Tool:** `remove_dependency` +* **CLI Command:** `task-master remove-dependency [options]` +* **Description:** `Remove a dependency relationship between two Taskmaster tasks.` +* **Key Parameters/Options:** + * `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`) + * `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`) + * `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Update task relationships when the order of execution changes. + +### 20. Validate Dependencies (`validate_dependencies`) + +* **MCP Tool:** `validate_dependencies` +* **CLI Command:** `task-master validate-dependencies [options]` +* **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.` +* **Key Parameters/Options:** + * `tag`: `Specify which tag context to validate. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Audit the integrity of your task dependencies. + +### 21. Fix Dependencies (`fix_dependencies`) + +* **MCP Tool:** `fix_dependencies` +* **CLI Command:** `task-master fix-dependencies [options]` +* **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.` +* **Key Parameters/Options:** + * `tag`: `Specify which tag context to fix dependencies in. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Clean up dependency errors automatically. + +--- + +## Analysis & Reporting + +### 22. Analyze Project Complexity (`analyze_project_complexity`) + +* **MCP Tool:** `analyze_project_complexity` +* **CLI Command:** `task-master analyze-complexity [options]` +* **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.` +* **Key Parameters/Options:** + * `output`: `Where to save the complexity analysis report. Default is '.taskmaster/reports/task-complexity-report.json' (or '..._tagname.json' if a tag is used).` (CLI: `-o, --output <file>`) + * `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`) + * `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`) + * `tag`: `Specify which tag context to analyze. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Used before breaking down tasks to identify which ones need the most attention. +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. + +### 23. View Complexity Report (`complexity_report`) + +* **MCP Tool:** `complexity_report` +* **CLI Command:** `task-master complexity-report [options]` +* **Description:** `Display the task complexity analysis report in a readable format.` +* **Key Parameters/Options:** + * `tag`: `Specify which tag context to show the report for. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to the complexity report (default: '.taskmaster/reports/task-complexity-report.json').` (CLI: `-f, --file <file>`) +* **Usage:** Review and understand the complexity analysis results after running analyze-complexity. + +--- + +## File Management + +### 24. Generate Task Files (`generate`) + +* **MCP Tool:** `generate` +* **CLI Command:** `task-master generate [options]` +* **Description:** `Create or update individual Markdown files for each task based on your tasks.json.` +* **Key Parameters/Options:** + * `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`) + * `tag`: `Specify which tag context to generate files for. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) +* **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. This command is now manual and no longer runs automatically. + +--- + +## AI-Powered Research + +### 25. Research (`research`) + +* **MCP Tool:** `research` +* **CLI Command:** `task-master research [options]` +* **Description:** `Perform AI-powered research queries with project context to get fresh, up-to-date information beyond the AI's knowledge cutoff.` +* **Key Parameters/Options:** + * `query`: `Required. Research query/prompt (e.g., "What are the latest best practices for React Query v5?").` (CLI: `[query]` positional or `-q, --query <text>`) + * `taskIds`: `Comma-separated list of task/subtask IDs from the current tag context (e.g., "15,16.2,17").` (CLI: `-i, --id <ids>`) + * `filePaths`: `Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md").` (CLI: `-f, --files <paths>`) + * `customContext`: `Additional custom context text to include in the research.` (CLI: `-c, --context <text>`) + * `includeProjectTree`: `Include project file tree structure in context (default: false).` (CLI: `--tree`) + * `detailLevel`: `Detail level for the research response: 'low', 'medium', 'high' (default: medium).` (CLI: `--detail <level>`) + * `saveTo`: `Task or subtask ID (e.g., "15", "15.2") to automatically save the research conversation to.` (CLI: `--save-to <id>`) + * `saveFile`: `If true, saves the research conversation to a markdown file in '.taskmaster/docs/research/'.` (CLI: `--save-file`) + * `noFollowup`: `Disables the interactive follow-up question menu in the CLI.` (CLI: `--no-followup`) + * `tag`: `Specify which tag context to use for task-based context gathering. Defaults to the current active tag.` (CLI: `--tag <name>`) + * `projectRoot`: `The directory of the project. Must be an absolute path.` (CLI: Determined automatically) +* **Usage:** **This is a POWERFUL tool that agents should use FREQUENTLY** to: + * Get fresh information beyond knowledge cutoff dates + * Research latest best practices, library updates, security patches + * Find implementation examples for specific technologies + * Validate approaches against current industry standards + * Get contextual advice based on project files and tasks +* **When to Consider Using Research:** + * **Before implementing any task** - Research current best practices + * **When encountering new technologies** - Get up-to-date implementation guidance (libraries, apis, etc) + * **For security-related tasks** - Find latest security recommendations + * **When updating dependencies** - Research breaking changes and migration guides + * **For performance optimization** - Get current performance best practices + * **When debugging complex issues** - Research known solutions and workarounds +* **Research + Action Pattern:** + * Use `research` to gather fresh information + * Use `update_subtask` to commit findings with timestamps + * Use `update_task` to incorporate research into task details + * Use `add_task` with research flag for informed task creation +* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. The research provides FRESH data beyond the AI's training cutoff, making it invaluable for current best practices and recent developments. + +--- + +## Tag Management + +This new suite of commands allows you to manage different task contexts (tags). + +### 26. List Tags (`tags`) + +* **MCP Tool:** `list_tags` +* **CLI Command:** `task-master tags [options]` +* **Description:** `List all available tags with task counts, completion status, and other metadata.` +* **Key Parameters/Options:** + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) + * `--show-metadata`: `Include detailed metadata in the output (e.g., creation date, description).` (CLI: `--show-metadata`) + +### 27. Add Tag (`add_tag`) + +* **MCP Tool:** `add_tag` +* **CLI Command:** `task-master add-tag <tagName> [options]` +* **Description:** `Create a new, empty tag context, or copy tasks from another tag.` +* **Key Parameters/Options:** + * `tagName`: `Name of the new tag to create (alphanumeric, hyphens, underscores).` (CLI: `<tagName>` positional) + * `--from-branch`: `Creates a tag with a name derived from the current git branch, ignoring the <tagName> argument.` (CLI: `--from-branch`) + * `--copy-from-current`: `Copy tasks from the currently active tag to the new tag.` (CLI: `--copy-from-current`) + * `--copy-from <tag>`: `Copy tasks from a specific source tag to the new tag.` (CLI: `--copy-from <tag>`) + * `--description <text>`: `Provide an optional description for the new tag.` (CLI: `-d, --description <text>`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) + +### 28. Delete Tag (`delete_tag`) + +* **MCP Tool:** `delete_tag` +* **CLI Command:** `task-master delete-tag <tagName> [options]` +* **Description:** `Permanently delete a tag and all of its associated tasks.` +* **Key Parameters/Options:** + * `tagName`: `Name of the tag to delete.` (CLI: `<tagName>` positional) + * `--yes`: `Skip the confirmation prompt.` (CLI: `-y, --yes`) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) + +### 29. Use Tag (`use_tag`) + +* **MCP Tool:** `use_tag` +* **CLI Command:** `task-master use-tag <tagName>` +* **Description:** `Switch your active task context to a different tag.` +* **Key Parameters/Options:** + * `tagName`: `Name of the tag to switch to.` (CLI: `<tagName>` positional) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) + +### 30. Rename Tag (`rename_tag`) + +* **MCP Tool:** `rename_tag` +* **CLI Command:** `task-master rename-tag <oldName> <newName>` +* **Description:** `Rename an existing tag.` +* **Key Parameters/Options:** + * `oldName`: `The current name of the tag.` (CLI: `<oldName>` positional) + * `newName`: `The new name for the tag.` (CLI: `<newName>` positional) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`) + +### 31. Copy Tag (`copy_tag`) + +* **MCP Tool:** `copy_tag` +* **CLI Command:** `task-master copy-tag <sourceName> <targetName> [options]` +* **Description:** `Copy an entire tag context, including all its tasks and metadata, to a new tag.` +* **Key Parameters/Options:** + * `sourceName`: `Name of the tag to copy from.` (CLI: `<sourceName>` positional) + * `targetName`: `Name of the new tag to create.` (CLI: `<targetName>` positional) + * `--description <text>`: `Optional description for the new tag.` (CLI: `-d, --description <text>`) + +--- + +## Miscellaneous + +### 32. Sync Readme (`sync-readme`) -- experimental + +* **MCP Tool:** N/A +* **CLI Command:** `task-master sync-readme [options]` +* **Description:** `Exports your task list to your project's README.md file, useful for showcasing progress.` +* **Key Parameters/Options:** + * `status`: `Filter tasks by status (e.g., 'pending', 'done').` (CLI: `-s, --status <status>`) + * `withSubtasks`: `Include subtasks in the export.` (CLI: `--with-subtasks`) + * `tag`: `Specify which tag context to export from. Defaults to the current active tag.` (CLI: `--tag <name>`) + +--- + +## Environment Variables Configuration (Updated) + +Taskmaster primarily uses the **`.taskmaster/config.json`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`. + +Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL: + +* **API Keys (Required for corresponding provider):** + * `ANTHROPIC_API_KEY` + * `PERPLEXITY_API_KEY` + * `OPENAI_API_KEY` + * `GOOGLE_API_KEY` + * `MISTRAL_API_KEY` + * `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too) + * `OPENROUTER_API_KEY` + * `XAI_API_KEY` + * `OLLAMA_API_KEY` (Requires `OLLAMA_BASE_URL` too) +* **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):** + * `AZURE_OPENAI_ENDPOINT` + * `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`) + +**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.kiro/mcp.json`** file (for MCP/Kiro integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmaster/config.json` via `task-master models` command or `models` MCP tool. + +--- + +For details on how these commands fit into the development process, see the [dev_workflow.md](.kiro/steering/dev_workflow.md). \ No newline at end of file diff --git a/.metadata b/.metadata index ff2d6f1..3f600b8 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" + revision: "077b4a4ce10a07b82caa6897f0c626f9c0a3ac90" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: android - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: ios - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: linux - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: macos - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: web - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 - platform: windows - create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 - base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 + base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90 # User provided section diff --git a/.taskmaster/config.json b/.taskmaster/config.json new file mode 100644 index 0000000..d506c55 --- /dev/null +++ b/.taskmaster/config.json @@ -0,0 +1,36 @@ +{ + "models": { + "main": { + "provider": "ollama", + "modelId": "devstral:24b", + "maxTokens": 64000, + "temperature": 0.2 + }, + "research": { + "provider": "ollama", + "modelId": "gemma3:12b", + "maxTokens": 8700, + "temperature": 0.1 + }, + "fallback": { + "provider": "ollama", + "modelId": "phi4-mini:latest", + "maxTokens": 8192, + "temperature": 0.2 + } + }, + "global": { + "logLevel": "info", + "debug": false, + "defaultNumTasks": 10, + "defaultSubtasks": 5, + "defaultPriority": "medium", + "projectName": "Task Master", + "ollamaBaseURL": "http://127.0.0.1:11434/api", + "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", + "responseLanguage": "English", + "userId": "1234567890", + "defaultTag": "master" + }, + "claudeCode": {} +} diff --git a/.taskmaster/docs/prd.txt b/.taskmaster/docs/prd.txt new file mode 100644 index 0000000..e9ca1e6 --- /dev/null +++ b/.taskmaster/docs/prd.txt @@ -0,0 +1,59 @@ + +# Product Requirements Document: Firebase Authentication Flutter DDD App + +## 1. Overview + +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. + +## 2. Key Features and Improvements + +### 2.1. Riverpod 3.0 Migration + +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: + +- Updating the `hooks_riverpod` dependency in `pubspec.yaml`. +- Refactoring the existing providers and widgets to align with the latest Riverpod 3.0 APIs. +- Ensuring that the state management is robust and efficient. + +### 2.2. UI Modernization + +The current user interface is basic and can be significantly improved. The new UI should be: + +- **Modern and visually appealing:** Adopt a clean and modern design language (e.g., Material You). +- **User-friendly:** The user experience should be intuitive and seamless. +- **Responsive:** The UI should adapt to different screen sizes and orientations. + +This will involve: + +- Redesigning the login, registration, and home pages. +- Creating a consistent theme and style guide. +- Adding animations and transitions to enhance the user experience. + +### 2.3. Code Refactoring and Cleanup + +The codebase should be refactored to improve its quality, readability, and maintainability. This includes: + +- **Updating dependencies:** All dependencies should be updated to their latest stable versions. +- **Improving error handling:** Implement a more robust error handling mechanism to gracefully handle exceptions and provide clear feedback to the user. +- **Enhancing validation:** Strengthen the input validation for the login and registration forms. +- **Code organization:** Ensure that the code is well-organized and follows the principles of Domain-Driven Design (DDD). + +### 2.4. Enhanced Authentication Flow + +The authentication flow can be improved by adding the following features: + +- **Password reset:** Allow users to reset their passwords if they forget them. +- **Email verification:** Send a verification email to new users to ensure that they have provided a valid email address. +- **Social logins:** Add support for social logins (e.g., Google, Apple) to provide users with more options for signing in. + +## 3. Non-Functional Requirements + +- **Performance:** The application should be performant and responsive, with minimal jank or lag. +- **Security:** The application should be secure and protect user data. +- **Scalability:** The architecture should be scalable to accommodate future growth and new features. + +## 4. Future Enhancements + +- **Profile page:** A user profile page where users can view and edit their information. +- **Two-factor authentication:** Add an extra layer of security with two-factor authentication. +- **Offline support:** Cache data locally to provide a seamless experience even when the user is offline. diff --git a/.taskmaster/state.json b/.taskmaster/state.json new file mode 100644 index 0000000..49f0a26 --- /dev/null +++ b/.taskmaster/state.json @@ -0,0 +1,6 @@ +{ + "currentTag": "master", + "lastSwitched": "2025-07-21T07:37:41.540Z", + "branchTagMapping": {}, + "migrationNoticeShown": true +} \ No newline at end of file diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json new file mode 100644 index 0000000..029b6f5 --- /dev/null +++ b/.taskmaster/tasks/tasks.json @@ -0,0 +1,99 @@ +{ + "master": { + "tasks": [ + { + "id": 1, + "title": "Migrate to Riverpod 3.0", + "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.", + "status": "done", + "priority": "high", + "dependencies": [] + }, + { + "id": 2, + "title": "Modernize the User Interface", + "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.", + "status": "pending", + "priority": "high", + "dependencies": [ + 1 + ] + }, + { + "id": 3, + "title": "Refactor and Clean Up Codebase", + "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.", + "status": "pending", + "priority": "medium", + "dependencies": [ + 1 + ] + }, + { + "id": 4, + "title": "Implement Password Reset", + "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.", + "status": "pending", + "priority": "medium", + "dependencies": [ + 1 + ] + }, + { + "id": 5, + "title": "Implement Email Verification", + "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.", + "status": "pending", + "priority": "medium", + "dependencies": [ + 1 + ] + }, + { + "id": 6, + "title": "Add Social Logins", + "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.", + "status": "pending", + "priority": "low", + "dependencies": [ + 1 + ] + }, + { + "id": 7, + "title": "Create User Profile Page", + "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.", + "status": "pending", + "priority": "low", + "dependencies": [ + 1 + ] + }, + { + "id": 8, + "title": "Implement Two-Factor Authentication", + "description": "Add an extra layer of security with two-factor authentication. This will help to protect user accounts from unauthorized access.", + "status": "pending", + "priority": "low", + "dependencies": [ + 1 + ] + }, + { + "id": 9, + "title": "Implement Offline Support", + "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.", + "status": "pending", + "priority": "low", + "dependencies": [ + 1 + ] + } + ], + "metadata": { + "created": "2025-07-21T07:47:36.467Z", + "updated": "2025-07-21T10:19:22.468Z", + "description": "Tasks for master context" + } + } +} \ No newline at end of file diff --git a/.taskmaster/templates/example_prd.txt b/.taskmaster/templates/example_prd.txt new file mode 100644 index 0000000..194114d --- /dev/null +++ b/.taskmaster/templates/example_prd.txt @@ -0,0 +1,47 @@ +<context> +# Overview +[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.] + +# Core Features +[List and describe the main features of your product. For each feature, include: +- What it does +- Why it's important +- How it works at a high level] + +# User Experience +[Describe the user journey and experience. Include: +- User personas +- Key user flows +- UI/UX considerations] +</context> +<PRD> +# Technical Architecture +[Outline the technical implementation details: +- System components +- Data models +- APIs and integrations +- Infrastructure requirements] + +# Development Roadmap +[Break down the development process into phases: +- MVP requirements +- Future enhancements +- 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] + +# Logical Dependency Chain +[Define the logical order of development: +- Which features need to be built first (foundation) +- Getting as quickly as possible to something usable/visible front end that works +- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches] + +# Risks and Mitigations +[Identify potential risks and how they'll be addressed: +- Technical challenges +- Figuring out the MVP that we can build upon +- Resource constraints] + +# Appendix +[Include any additional information: +- Research findings +- Technical specifications] +</PRD> \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fdffc89 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,438 @@ +# Task Master AI - Agent Integration Guide + +## Essential Commands + +### Core Workflow Commands + +```bash +# Project Setup +task-master init # Initialize Task Master in current project +task-master parse-prd .taskmaster/docs/prd.txt # Generate tasks from PRD document +task-master models --setup # Configure AI models interactively + +# Daily Development Workflow +task-master list # Show all tasks with status +task-master next # Get next available task to work on +task-master show <id> # View detailed task information (e.g., task-master show 1.2) +task-master set-status --id=<id> --status=done # Mark task complete + +# Task Management +task-master add-task --prompt="description" --research # Add new task with AI assistance +task-master expand --id=<id> --research --force # Break task into subtasks +task-master update-task --id=<id> --prompt="changes" # Update specific task +task-master update --from=<id> --prompt="changes" # Update multiple tasks from ID onwards +task-master update-subtask --id=<id> --prompt="notes" # Add implementation notes to subtask + +# Analysis & Planning +task-master analyze-complexity --research # Analyze task complexity +task-master complexity-report # View complexity analysis +task-master expand --all --research # Expand all eligible tasks + +# Dependencies & Organization +task-master add-dependency --id=<id> --depends-on=<id> # Add task dependency +task-master move --from=<id> --to=<id> # Reorganize task hierarchy +task-master validate-dependencies # Check for dependency issues +task-master generate # Update task markdown files (usually auto-called) +``` + +## Key Files & Project Structure + +### Core Files + +- `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed) +- `.taskmaster/config.json` - AI model configuration (use `task-master models`to + modify) +- `.taskmaster/docs/prd.txt` - Product Requirements Document for parsing +- `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from + tasks.json) +- `.env` - API keys for CLI usage + +### Claude Code Integration Files + +- `CLAUDE.md` - Auto-loaded context for Claude Code (this file) +- `.claude/settings.json` - Claude Code tool allowlist and preferences +- `.claude/commands/` - Custom slash commands for repeated workflows +- `.mcp.json` - MCP server configuration (project-specific) + +### Directory Structure + +``` +project/ +├── .taskmaster/ +│ ├── tasks/ # Task files directory +│ │ ├── tasks.json # Main task database +│ │ ├── task-1.md # Individual task files +│ │ └── task-2.md +│ ├── docs/ # Documentation directory +│ │ ├── prd.txt # Product requirements +│ ├── reports/ # Analysis reports directory +│ │ └── task-complexity-report.json +│ ├── templates/ # Template files +│ │ └── example_prd.txt # Example PRD template +│ └── config.json # AI models & settings +├── .claude/ +│ ├── settings.json # Claude Code configuration +│ └── commands/ # Custom slash commands +├── .env # API keys +├── .mcp.json # MCP configuration +└── CLAUDE.md # This file - auto-loaded by Claude Code +``` + +## MCP Integration + +Task Master provides an MCP server that Claude Code can connect to. Configure in +`.mcp.json`: + +```json +{ + "mcpServers": { + "task-master-ai": { + "command": "npx", + "args": [ + "-y", + "--package=task-master-ai", + "task-master-ai" + ], + "env": { + "ANTHROPIC_API_KEY": "your_key_here", + "PERPLEXITY_API_KEY": "your_key_here", + "OPENAI_API_KEY": "OPENAI_API_KEY_HERE", + "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE", + "XAI_API_KEY": "XAI_API_KEY_HERE", + "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE", + "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE", + "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE", + "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE" + } + } + } +} +``` + +### Essential MCP Tools + +```javascript +help; // = shows available taskmaster commands +// Project setup +initialize_project; // = task-master init +parse_prd; // = task-master parse-prd + +// Daily workflow +get_tasks; // = task-master list +next_task; // = task-master next +get_task; // = task-master show <id> +set_task_status; // = task-master set-status + +// Task management +add_task; // = task-master add-task +expand_task; // = task-master expand +update_task; // = task-master update-task +update_subtask; // = task-master update-subtask +update; // = task-master update + +// Analysis +analyze_project_complexity; // = task-master analyze-complexity +complexity_report; // = task-master complexity-report +``` + +## Claude Code Workflow Integration + +### Standard Development Workflow + +#### 1. Project Initialization + +```bash +# Initialize Task Master +task-master init + +# Create or obtain PRD, then parse it +task-master parse-prd .taskmaster/docs/prd.txt + +# Analyze complexity and expand tasks +task-master analyze-complexity --research +task-master expand --all --research +``` + +If tasks already exist, another PRD can be parsed (with new information only!) +using parse-prd with --append flag. This will add the generated tasks to the +existing list of tasks.. + +#### 2. Daily Development Loop + +```bash +# Start each session +task-master next # Find next available task +task-master show <id> # Review task details + +# During implementation, check in code context into the tasks and subtasks +task-master update-subtask --id=<id> --prompt="implementation notes..." + +# Complete tasks +task-master set-status --id=<id> --status=done +``` + +#### 3. Multi-Claude Workflows + +For complex projects, use multiple Claude Code sessions: + +```bash +# Terminal 1: Main implementation +cd project && claude + +# Terminal 2: Testing and validation +cd project-test-worktree && claude + +# Terminal 3: Documentation updates +cd project-docs-worktree && claude +``` + +### Custom Slash Commands + +Create `.claude/commands/taskmaster-next.md`: + +```markdown +Find the next available Task Master task and show its details. + +Steps: + +1. Run `task-master next` to get the next task +2. If a task is available, run `task-master show <id>` for full details +3. Provide a summary of what needs to be implemented +4. Suggest the first implementation step +``` + +Create `.claude/commands/taskmaster-complete.md`: + +```markdown +Complete a Task Master task: $ARGUMENTS + +Steps: + +1. Review the current task with `task-master show $ARGUMENTS` +2. Verify all implementation is complete +3. Run any tests related to this task +4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done` +5. Show the next available task with `task-master next` +``` + +## Tool Allowlist Recommendations + +Add to `.claude/settings.json`: + +```json +{ + "allowedTools": [ + "Edit", + "Bash(task-master *)", + "Bash(git commit:*)", + "Bash(git add:*)", + "Bash(npm run *)", + "mcp__task_master_ai__*" + ] +} +``` + +## Configuration & Setup + +### API Keys Required + +At least **one** of these API keys must be configured: + +- `ANTHROPIC_API_KEY` (Claude models) - **Recommended** +- `PERPLEXITY_API_KEY` (Research features) - **Highly recommended** +- `OPENAI_API_KEY` (GPT models) +- `GOOGLE_API_KEY` (Gemini models) +- `MISTRAL_API_KEY` (Mistral models) +- `OPENROUTER_API_KEY` (Multiple models) +- `XAI_API_KEY` (Grok models) + +An API key is required for any provider used across any of the 3 roles defined +in the `models` command. + +### Model Configuration + +```bash +# Interactive setup (recommended) +task-master models --setup + +# Set specific models +task-master models --set-main claude-3-5-sonnet-20241022 +task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online +task-master models --set-fallback gpt-4o-mini +``` + +## Task Structure & IDs + +### Task ID Format + +- Main tasks: `1`, `2`, `3`, etc. +- Subtasks: `1.1`, `1.2`, `2.1`, etc. +- Sub-subtasks: `1.1.1`, `1.1.2`, etc. + +### Task Status Values + +- `pending` - Ready to work on +- `in-progress` - Currently being worked on +- `done` - Completed and verified +- `deferred` - Postponed +- `cancelled` - No longer needed +- `blocked` - Waiting on external factors + +### Task Fields + +```json +{ + "id": "1.2", + "title": "Implement user authentication", + "description": "Set up JWT-based auth system", + "status": "pending", + "priority": "high", + "dependencies": [ + "1.1" + ], + "details": "Use bcrypt for hashing, JWT for tokens...", + "testStrategy": "Unit tests for auth functions, integration tests for login flow", + "subtasks": [] +} +``` + +## Claude Code Best Practices with Task Master + +### Context Management + +- Use `/clear` between different tasks to maintain focus +- This CLAUDE.md file is automatically loaded for context +- Use `task-master show <id>` to pull specific task context when needed + +### Iterative Implementation + +1. `task-master show <subtask-id>` - Understand requirements +2. Explore codebase and plan implementation +3. `task-master update-subtask --id=<id> --prompt="detailed plan"` - Log plan +4. `task-master set-status --id=<id> --status=in-progress` - Start work +5. Implement code following logged plan +6. `task-master update-subtask --id=<id> --prompt="what worked/didn't work"` - + Log progress +7. `task-master set-status --id=<id> --status=done` - Complete task + +### Complex Workflows with Checklists + +For large migrations or multi-step processes: + +1. Create a markdown PRD file describing the new changes: + `touch task-migration-checklist.md` (prds can be .txt or .md) +2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` ( + also available in MCP) +3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier + using `analyze-complexity` with the correct --to and --from IDs (the new ids) + to identify the ideal subtask amounts for each task. Then expand them. +4. Work through items systematically, checking them off as completed +5. Use `task-master update-subtask` to log progress on each task/subtask and/or + updating/researching them before/during implementation if getting stuck + +### Git Integration + +Task Master works well with `gh` CLI: + +```bash +# Create PR for completed task +gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2" + +# Reference task in commits +git commit -m "feat: implement JWT auth (task 1.2)" +``` + +### Parallel Development with Git Worktrees + +```bash +# Create worktrees for parallel task development +git worktree add ../project-auth feature/auth-system +git worktree add ../project-api feature/api-refactor + +# Run Claude Code in each worktree +cd ../project-auth && claude # Terminal 1: Auth work +cd ../project-api && claude # Terminal 2: API work +``` + +## Troubleshooting + +### AI Commands Failing + +```bash +# Check API keys are configured +cat .env # For CLI usage + +# Verify model configuration +task-master models + +# Test with different model +task-master models --set-fallback gpt-4o-mini +``` + +### MCP Connection Issues + +- Check `.mcp.json` configuration +- Verify Node.js installation +- Use `--mcp-debug` flag when starting Claude Code +- Use CLI as fallback if MCP unavailable + +### Task File Sync Issues + +```bash +# Regenerate task files from tasks.json +task-master generate + +# Fix dependency issues +task-master fix-dependencies +``` + +DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same +Taskmaster core files. + +## Important Notes + +### AI-Powered Operations + +These commands make AI calls and may take up to a minute: + +- `parse_prd` / `task-master parse-prd` +- `analyze_project_complexity` / `task-master analyze-complexity` +- `expand_task` / `task-master expand` +- `expand_all` / `task-master expand --all` +- `add_task` / `task-master add-task` +- `update` / `task-master update` +- `update_task` / `task-master update-task` +- `update_subtask` / `task-master update-subtask` + +### File Management + +- Never manually edit `tasks.json` - use commands instead +- Never manually edit `.taskmaster/config.json` - use `task-master models` +- Task markdown files in `tasks/` are auto-generated +- Run `task-master generate` after manual changes to tasks.json + +### Claude Code Session Management + +- Use `/clear` frequently to maintain focused context +- Create custom slash commands for repeated Task Master workflows +- Configure tool allowlist to streamline permissions +- Use headless mode for automation: `claude -p "task-master next"` + +### Multi-Task Updates + +- Use `update --from=<id>` to update multiple future tasks +- Use `update-task --id=<id>` for single task updates +- Use `update-subtask --id=<id>` for implementation logging + +### Research Mode + +- Add `--research` flag for research-based AI enhancement +- Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in + environment +- Provides more informed task creation and updates +- Recommended for complex technical tasks + +--- + +_This guide ensures Claude Code has immediate access to Task Master's essential +functionality for agentic development workflows._ diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..fdffc89 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,438 @@ +# Task Master AI - Agent Integration Guide + +## Essential Commands + +### Core Workflow Commands + +```bash +# Project Setup +task-master init # Initialize Task Master in current project +task-master parse-prd .taskmaster/docs/prd.txt # Generate tasks from PRD document +task-master models --setup # Configure AI models interactively + +# Daily Development Workflow +task-master list # Show all tasks with status +task-master next # Get next available task to work on +task-master show <id> # View detailed task information (e.g., task-master show 1.2) +task-master set-status --id=<id> --status=done # Mark task complete + +# Task Management +task-master add-task --prompt="description" --research # Add new task with AI assistance +task-master expand --id=<id> --research --force # Break task into subtasks +task-master update-task --id=<id> --prompt="changes" # Update specific task +task-master update --from=<id> --prompt="changes" # Update multiple tasks from ID onwards +task-master update-subtask --id=<id> --prompt="notes" # Add implementation notes to subtask + +# Analysis & Planning +task-master analyze-complexity --research # Analyze task complexity +task-master complexity-report # View complexity analysis +task-master expand --all --research # Expand all eligible tasks + +# Dependencies & Organization +task-master add-dependency --id=<id> --depends-on=<id> # Add task dependency +task-master move --from=<id> --to=<id> # Reorganize task hierarchy +task-master validate-dependencies # Check for dependency issues +task-master generate # Update task markdown files (usually auto-called) +``` + +## Key Files & Project Structure + +### Core Files + +- `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed) +- `.taskmaster/config.json` - AI model configuration (use `task-master models`to + modify) +- `.taskmaster/docs/prd.txt` - Product Requirements Document for parsing +- `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from + tasks.json) +- `.env` - API keys for CLI usage + +### Claude Code Integration Files + +- `CLAUDE.md` - Auto-loaded context for Claude Code (this file) +- `.claude/settings.json` - Claude Code tool allowlist and preferences +- `.claude/commands/` - Custom slash commands for repeated workflows +- `.mcp.json` - MCP server configuration (project-specific) + +### Directory Structure + +``` +project/ +├── .taskmaster/ +│ ├── tasks/ # Task files directory +│ │ ├── tasks.json # Main task database +│ │ ├── task-1.md # Individual task files +│ │ └── task-2.md +│ ├── docs/ # Documentation directory +│ │ ├── prd.txt # Product requirements +│ ├── reports/ # Analysis reports directory +│ │ └── task-complexity-report.json +│ ├── templates/ # Template files +│ │ └── example_prd.txt # Example PRD template +│ └── config.json # AI models & settings +├── .claude/ +│ ├── settings.json # Claude Code configuration +│ └── commands/ # Custom slash commands +├── .env # API keys +├── .mcp.json # MCP configuration +└── CLAUDE.md # This file - auto-loaded by Claude Code +``` + +## MCP Integration + +Task Master provides an MCP server that Claude Code can connect to. Configure in +`.mcp.json`: + +```json +{ + "mcpServers": { + "task-master-ai": { + "command": "npx", + "args": [ + "-y", + "--package=task-master-ai", + "task-master-ai" + ], + "env": { + "ANTHROPIC_API_KEY": "your_key_here", + "PERPLEXITY_API_KEY": "your_key_here", + "OPENAI_API_KEY": "OPENAI_API_KEY_HERE", + "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE", + "XAI_API_KEY": "XAI_API_KEY_HERE", + "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE", + "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE", + "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE", + "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE" + } + } + } +} +``` + +### Essential MCP Tools + +```javascript +help; // = shows available taskmaster commands +// Project setup +initialize_project; // = task-master init +parse_prd; // = task-master parse-prd + +// Daily workflow +get_tasks; // = task-master list +next_task; // = task-master next +get_task; // = task-master show <id> +set_task_status; // = task-master set-status + +// Task management +add_task; // = task-master add-task +expand_task; // = task-master expand +update_task; // = task-master update-task +update_subtask; // = task-master update-subtask +update; // = task-master update + +// Analysis +analyze_project_complexity; // = task-master analyze-complexity +complexity_report; // = task-master complexity-report +``` + +## Claude Code Workflow Integration + +### Standard Development Workflow + +#### 1. Project Initialization + +```bash +# Initialize Task Master +task-master init + +# Create or obtain PRD, then parse it +task-master parse-prd .taskmaster/docs/prd.txt + +# Analyze complexity and expand tasks +task-master analyze-complexity --research +task-master expand --all --research +``` + +If tasks already exist, another PRD can be parsed (with new information only!) +using parse-prd with --append flag. This will add the generated tasks to the +existing list of tasks.. + +#### 2. Daily Development Loop + +```bash +# Start each session +task-master next # Find next available task +task-master show <id> # Review task details + +# During implementation, check in code context into the tasks and subtasks +task-master update-subtask --id=<id> --prompt="implementation notes..." + +# Complete tasks +task-master set-status --id=<id> --status=done +``` + +#### 3. Multi-Claude Workflows + +For complex projects, use multiple Claude Code sessions: + +```bash +# Terminal 1: Main implementation +cd project && claude + +# Terminal 2: Testing and validation +cd project-test-worktree && claude + +# Terminal 3: Documentation updates +cd project-docs-worktree && claude +``` + +### Custom Slash Commands + +Create `.claude/commands/taskmaster-next.md`: + +```markdown +Find the next available Task Master task and show its details. + +Steps: + +1. Run `task-master next` to get the next task +2. If a task is available, run `task-master show <id>` for full details +3. Provide a summary of what needs to be implemented +4. Suggest the first implementation step +``` + +Create `.claude/commands/taskmaster-complete.md`: + +```markdown +Complete a Task Master task: $ARGUMENTS + +Steps: + +1. Review the current task with `task-master show $ARGUMENTS` +2. Verify all implementation is complete +3. Run any tests related to this task +4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done` +5. Show the next available task with `task-master next` +``` + +## Tool Allowlist Recommendations + +Add to `.claude/settings.json`: + +```json +{ + "allowedTools": [ + "Edit", + "Bash(task-master *)", + "Bash(git commit:*)", + "Bash(git add:*)", + "Bash(npm run *)", + "mcp__task_master_ai__*" + ] +} +``` + +## Configuration & Setup + +### API Keys Required + +At least **one** of these API keys must be configured: + +- `ANTHROPIC_API_KEY` (Claude models) - **Recommended** +- `PERPLEXITY_API_KEY` (Research features) - **Highly recommended** +- `OPENAI_API_KEY` (GPT models) +- `GOOGLE_API_KEY` (Gemini models) +- `MISTRAL_API_KEY` (Mistral models) +- `OPENROUTER_API_KEY` (Multiple models) +- `XAI_API_KEY` (Grok models) + +An API key is required for any provider used across any of the 3 roles defined +in the `models` command. + +### Model Configuration + +```bash +# Interactive setup (recommended) +task-master models --setup + +# Set specific models +task-master models --set-main claude-3-5-sonnet-20241022 +task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online +task-master models --set-fallback gpt-4o-mini +``` + +## Task Structure & IDs + +### Task ID Format + +- Main tasks: `1`, `2`, `3`, etc. +- Subtasks: `1.1`, `1.2`, `2.1`, etc. +- Sub-subtasks: `1.1.1`, `1.1.2`, etc. + +### Task Status Values + +- `pending` - Ready to work on +- `in-progress` - Currently being worked on +- `done` - Completed and verified +- `deferred` - Postponed +- `cancelled` - No longer needed +- `blocked` - Waiting on external factors + +### Task Fields + +```json +{ + "id": "1.2", + "title": "Implement user authentication", + "description": "Set up JWT-based auth system", + "status": "pending", + "priority": "high", + "dependencies": [ + "1.1" + ], + "details": "Use bcrypt for hashing, JWT for tokens...", + "testStrategy": "Unit tests for auth functions, integration tests for login flow", + "subtasks": [] +} +``` + +## Claude Code Best Practices with Task Master + +### Context Management + +- Use `/clear` between different tasks to maintain focus +- This CLAUDE.md file is automatically loaded for context +- Use `task-master show <id>` to pull specific task context when needed + +### Iterative Implementation + +1. `task-master show <subtask-id>` - Understand requirements +2. Explore codebase and plan implementation +3. `task-master update-subtask --id=<id> --prompt="detailed plan"` - Log plan +4. `task-master set-status --id=<id> --status=in-progress` - Start work +5. Implement code following logged plan +6. `task-master update-subtask --id=<id> --prompt="what worked/didn't work"` - + Log progress +7. `task-master set-status --id=<id> --status=done` - Complete task + +### Complex Workflows with Checklists + +For large migrations or multi-step processes: + +1. Create a markdown PRD file describing the new changes: + `touch task-migration-checklist.md` (prds can be .txt or .md) +2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` ( + also available in MCP) +3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier + using `analyze-complexity` with the correct --to and --from IDs (the new ids) + to identify the ideal subtask amounts for each task. Then expand them. +4. Work through items systematically, checking them off as completed +5. Use `task-master update-subtask` to log progress on each task/subtask and/or + updating/researching them before/during implementation if getting stuck + +### Git Integration + +Task Master works well with `gh` CLI: + +```bash +# Create PR for completed task +gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2" + +# Reference task in commits +git commit -m "feat: implement JWT auth (task 1.2)" +``` + +### Parallel Development with Git Worktrees + +```bash +# Create worktrees for parallel task development +git worktree add ../project-auth feature/auth-system +git worktree add ../project-api feature/api-refactor + +# Run Claude Code in each worktree +cd ../project-auth && claude # Terminal 1: Auth work +cd ../project-api && claude # Terminal 2: API work +``` + +## Troubleshooting + +### AI Commands Failing + +```bash +# Check API keys are configured +cat .env # For CLI usage + +# Verify model configuration +task-master models + +# Test with different model +task-master models --set-fallback gpt-4o-mini +``` + +### MCP Connection Issues + +- Check `.mcp.json` configuration +- Verify Node.js installation +- Use `--mcp-debug` flag when starting Claude Code +- Use CLI as fallback if MCP unavailable + +### Task File Sync Issues + +```bash +# Regenerate task files from tasks.json +task-master generate + +# Fix dependency issues +task-master fix-dependencies +``` + +DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same +Taskmaster core files. + +## Important Notes + +### AI-Powered Operations + +These commands make AI calls and may take up to a minute: + +- `parse_prd` / `task-master parse-prd` +- `analyze_project_complexity` / `task-master analyze-complexity` +- `expand_task` / `task-master expand` +- `expand_all` / `task-master expand --all` +- `add_task` / `task-master add-task` +- `update` / `task-master update` +- `update_task` / `task-master update-task` +- `update_subtask` / `task-master update-subtask` + +### File Management + +- Never manually edit `tasks.json` - use commands instead +- Never manually edit `.taskmaster/config.json` - use `task-master models` +- Task markdown files in `tasks/` are auto-generated +- Run `task-master generate` after manual changes to tasks.json + +### Claude Code Session Management + +- Use `/clear` frequently to maintain focused context +- Create custom slash commands for repeated Task Master workflows +- Configure tool allowlist to streamline permissions +- Use headless mode for automation: `claude -p "task-master next"` + +### Multi-Task Updates + +- Use `update --from=<id>` to update multiple future tasks +- Use `update-task --id=<id>` for single task updates +- Use `update-subtask --id=<id>` for implementation logging + +### Research Mode + +- Add `--research` flag for research-based AI enhancement +- Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in + environment +- Provides more informed task creation and updates +- Recommended for complex technical tasks + +--- + +_This guide ensures Claude Code has immediate access to Task Master's essential +functionality for agentic development workflows._ diff --git a/README.md b/README.md index c9261d5..6592ffc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,45 @@ -# firebase_auth_flutter_ddd +# Firebase Authentication with Flutter & DDD -Firebase authentication example with Hooks Riverpod and Freezed following Flutter DDD architecture +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. + +This project serves as a robust starting point for building scalable and maintainable Flutter applications. + +## Features + +* **Firebase Authentication:** Email & Password sign-in. +* **Domain-Driven Design (DDD):** A clean and separated architecture to promote maintainability and testability. +* **Riverpod:** State management for a reactive and predictable state. +* **Freezed:** Code generation for immutable data classes and unions. +* **Error Handling:** Clear separation of domain and application failures. + +## Project Structure + +The project follows a layered architecture inspired by Domain-Driven Design. For a detailed explanation, please see the [Architecture Guide](./guides/architecture.md). + +``` +lib/ +├── application/ # Application Layer (Use Cases/BLoCs) +├── domain/ # Domain Layer (Entities, Value Objects, Interfaces) +├── services/ # Infrastructure Layer (Firebase Implementation) +└── screens/ # Presentation Layer (UI) +``` ## Getting Started -This project is a starting point for a Flutter application. +To get a local copy up and running, please follow the detailed instructions in the [Setup Guide](./guides/setup.md). + +## Architecture + +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). + +## Contributing -A few resources to get you started if this is your first Flutter project: +Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +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". -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..3373c71 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.firebase_auth_flutter_ddd" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.firebase_auth_flutter_ddd" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/main/kotlin/com/example/firebase_auth_flutter_ddd/MainActivity.kt b/android/app/src/main/kotlin/com/example/firebase_auth_flutter_ddd/MainActivity.kt new file mode 100644 index 0000000..f06e7dd --- /dev/null +++ b/android/app/src/main/kotlin/com/example/firebase_auth_flutter_ddd/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.firebase_auth_flutter_ddd + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register<Delete>("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/guides/architecture.md b/guides/architecture.md new file mode 100644 index 0000000..1a5e2fc --- /dev/null +++ b/guides/architecture.md @@ -0,0 +1,70 @@ +# Architecture Overview + +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. + +## Layers + +The project is structured into the following layers: + +* **Domain:** This is the core of the application and is independent of any other layer. It contains the business logic, entities, and value objects. +* **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). +* **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. +* **Screens (Presentation):** This is the UI layer of the application. It is responsible for displaying the data to the user and handling user input. + +## Directory Structure + +The project's directory structure reflects the layered architecture: + +``` +lib +├── application +│ └── authentication +├── domain +│ ├── authentication +│ └── core +├── screens +│ └── utils +└── services + └── authentication +``` + +### Domain Layer + +The `domain` directory contains the following: + +* **`authentication`:** This directory contains the core authentication-related business logic, including: + * `auth_failures.dart`: Defines the possible failures that can occur during authentication. + * `i_auth_facade.dart`: Defines the interface for the authentication service. + * `auth_value_objects.dart`: Defines the value objects used in the authentication domain. +* **`core`:** This directory contains the core building blocks of the domain layer, such as the `ValueObject` and `Error` classes. + +### Application Layer + +The `application` directory contains the following: + +* **`authentication`:** This directory contains the application-level logic for authentication, including: + * `auth_events.dart`: Defines the events that can be triggered by the UI. + * `auth_state_controller.dart`: The BLoC (or Riverpod equivalent) that manages the state of the authentication flow. + * `auth_states.dart`: Defines the different states of the authentication flow. + +### Services (Infrastructure) Layer + +The `services` directory contains the following: + +* **`authentication`:** This directory contains the implementation of the `IAuthFacade` interface, which interacts with Firebase Authentication. + +### Screens (Presentation) Layer + +The `screens` directory contains the following: + +* **`login_page.dart`:** The UI for the login screen. +* **`home_page.dart`:** The UI for the home screen, which is displayed after the user has successfully authenticated. +* **`utils`:** This directory contains utility widgets and functions used across the UI. + +## State Management + +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. + +## Code Generation + +This project uses the **Freezed** package to generate immutable classes and unions. The `build_runner` package is used to run the code generation. diff --git a/guides/setup.md b/guides/setup.md new file mode 100644 index 0000000..3d61f63 --- /dev/null +++ b/guides/setup.md @@ -0,0 +1,63 @@ +# Project Setup Guide + +This guide will walk you through setting up the project on your local machine. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +* **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). +* **Firebase Account:** You will need a Firebase account to connect the application to a Firebase project. + +## Setup Steps + +1. **Clone the Repository:** + + ```bash + git clone https://github.com/your-username/firebase_authentication_flutter_ddd.git + cd firebase_authentication_flutter_ddd + ``` + +2. **Configure Firebase:** + + * Create a new Firebase project on the [Firebase Console](https://console.firebase.google.com/). + * Follow the instructions to add a new Flutter application to your project. + * Download the `google-services.json` file for Android and the `GoogleService-Info.plist` file for iOS. + * Place the `google-services.json` file in the `android/app` directory. + * Place the `GoogleService-Info.plist` file in the `ios/Runner` directory. + +3. **Set Up Environment Variables:** + + * Create a `.env` file in the root of the project by copying the `.env.example` file: + + ```bash + cp .env.example .env + ``` + + * Open the `.env` file and add the necessary API keys. For this project, you will need to add your `OLLAMA_API_KEY`. + +4. **Install Dependencies:** + + * Run the following command to install the project's dependencies: + + ```bash + flutter pub get + ``` + +5. **Run the Application:** + + * You can now run the application on an emulator or a physical device: + + ```bash + flutter run + ``` + +## Build Runner + +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: + +```bash +flutter pub run build_runner build +``` + +This will regenerate the necessary files. diff --git a/lib/application/authentication/auth_events.freezed.dart b/lib/application/authentication/auth_events.freezed.dart index a8bf058..45e1a77 100644 --- a/lib/application/authentication/auth_events.freezed.dart +++ b/lib/application/authentication/auth_events.freezed.dart @@ -1,5 +1,5 @@ -// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file // ignore_for_file: type=lint // 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 @@ -9,188 +9,85 @@ part of 'auth_events.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity<T>(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$AuthEvents { - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? email) emailChanged, - required TResult Function(String? password) passwordChanged, - required TResult Function() signUpWithEmailAndPasswordPressed, - required TResult Function() signInWithEmailAndPasswordPressed, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? email)? emailChanged, - TResult? Function(String? password)? passwordChanged, - TResult? Function()? signUpWithEmailAndPasswordPressed, - TResult? Function()? signInWithEmailAndPasswordPressed, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? email)? emailChanged, - TResult Function(String? password)? passwordChanged, - TResult Function()? signUpWithEmailAndPasswordPressed, - TResult Function()? signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(EmailChanged value) emailChanged, - required TResult Function(PasswordChanged value) passwordChanged, - required TResult Function(SignUPWithEmailAndPasswordPressed value) - signUpWithEmailAndPasswordPressed, - required TResult Function(SignInWithEmailAndPasswordPressed value) - signInWithEmailAndPasswordPressed, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(EmailChanged value)? emailChanged, - TResult? Function(PasswordChanged value)? passwordChanged, - TResult? Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult? Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(EmailChanged value)? emailChanged, - TResult Function(PasswordChanged value)? passwordChanged, - TResult Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $AuthEventsCopyWith<$Res> { - factory $AuthEventsCopyWith( - AuthEvents value, $Res Function(AuthEvents) then) = - _$AuthEventsCopyWithImpl<$Res, AuthEvents>; -} - -/// @nodoc -class _$AuthEventsCopyWithImpl<$Res, $Val extends AuthEvents> - implements $AuthEventsCopyWith<$Res> { - _$AuthEventsCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; -} - -/// @nodoc -abstract class _$$EmailChangedImplCopyWith<$Res> { - factory _$$EmailChangedImplCopyWith( - _$EmailChangedImpl value, $Res Function(_$EmailChangedImpl) then) = - __$$EmailChangedImplCopyWithImpl<$Res>; - @useResult - $Res call({String? email}); -} - -/// @nodoc -class __$$EmailChangedImplCopyWithImpl<$Res> - extends _$AuthEventsCopyWithImpl<$Res, _$EmailChangedImpl> - implements _$$EmailChangedImplCopyWith<$Res> { - __$$EmailChangedImplCopyWithImpl( - _$EmailChangedImpl _value, $Res Function(_$EmailChangedImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? email = freezed, - }) { - return _then(_$EmailChangedImpl( - email: freezed == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$EmailChangedImpl implements EmailChanged { - const _$EmailChangedImpl({required this.email}); - - @override - final String? email; - - @override - String toString() { - return 'AuthEvents.emailChanged(email: $email)'; - } - @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$EmailChangedImpl && - (identical(other.email, email) || other.email == email)); + (other.runtimeType == runtimeType && other is AuthEvents); } @override - int get hashCode => Object.hash(runtimeType, email); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$EmailChangedImplCopyWith<_$EmailChangedImpl> get copyWith => - __$$EmailChangedImplCopyWithImpl<_$EmailChangedImpl>(this, _$identity); + int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? email) emailChanged, - required TResult Function(String? password) passwordChanged, - required TResult Function() signUpWithEmailAndPasswordPressed, - required TResult Function() signInWithEmailAndPasswordPressed, - }) { - return emailChanged(email); + String toString() { + return 'AuthEvents()'; } +} - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? email)? emailChanged, - TResult? Function(String? password)? passwordChanged, - TResult? Function()? signUpWithEmailAndPasswordPressed, - TResult? Function()? signInWithEmailAndPasswordPressed, - }) { - return emailChanged?.call(email); - } +/// @nodoc +class $AuthEventsCopyWith<$Res> { + $AuthEventsCopyWith(AuthEvents _, $Res Function(AuthEvents) __); +} + +/// Adds pattern-matching-related methods to [AuthEvents]. +extension AuthEventsPatterns on AuthEvents { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` - @override @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? email)? emailChanged, - TResult Function(String? password)? passwordChanged, - TResult Function()? signUpWithEmailAndPasswordPressed, - TResult Function()? signInWithEmailAndPasswordPressed, + TResult maybeMap<TResult extends Object?>({ + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(SignUPWithEmailAndPasswordPressed value)? + signUpWithEmailAndPasswordPressed, + TResult Function(SignInWithEmailAndPasswordPressed value)? + signInWithEmailAndPasswordPressed, required TResult orElse(), }) { - if (emailChanged != null) { - return emailChanged(email); + final _that = this; + switch (_that) { + case EmailChanged() when emailChanged != null: + return emailChanged(_that); + case PasswordChanged() when passwordChanged != null: + return passwordChanged(_that); + case SignUPWithEmailAndPasswordPressed() + when signUpWithEmailAndPasswordPressed != null: + return signUpWithEmailAndPasswordPressed(_that); + case SignInWithEmailAndPasswordPressed() + when signInWithEmailAndPasswordPressed != null: + return signInWithEmailAndPasswordPressed(_that); + case _: + return orElse(); } - return orElse(); } - @override + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + @optionalTypeArgs TResult map<TResult extends Object?>({ required TResult Function(EmailChanged value) emailChanged, @@ -200,10 +97,33 @@ class _$EmailChangedImpl implements EmailChanged { required TResult Function(SignInWithEmailAndPasswordPressed value) signInWithEmailAndPasswordPressed, }) { - return emailChanged(this); + final _that = this; + switch (_that) { + case EmailChanged(): + return emailChanged(_that); + case PasswordChanged(): + return passwordChanged(_that); + case SignUPWithEmailAndPasswordPressed(): + return signUpWithEmailAndPasswordPressed(_that); + case SignInWithEmailAndPasswordPressed(): + return signInWithEmailAndPasswordPressed(_that); + case _: + throw StateError('Unexpected subclass'); + } } - @override + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({ TResult? Function(EmailChanged value)? emailChanged, @@ -213,101 +133,73 @@ class _$EmailChangedImpl implements EmailChanged { TResult? Function(SignInWithEmailAndPasswordPressed value)? signInWithEmailAndPasswordPressed, }) { - return emailChanged?.call(this); + final _that = this; + switch (_that) { + case EmailChanged() when emailChanged != null: + return emailChanged(_that); + case PasswordChanged() when passwordChanged != null: + return passwordChanged(_that); + case SignUPWithEmailAndPasswordPressed() + when signUpWithEmailAndPasswordPressed != null: + return signUpWithEmailAndPasswordPressed(_that); + case SignInWithEmailAndPasswordPressed() + when signInWithEmailAndPasswordPressed != null: + return signInWithEmailAndPasswordPressed(_that); + case _: + return null; + } } - @override + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(EmailChanged value)? emailChanged, - TResult Function(PasswordChanged value)? passwordChanged, - TResult Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, + TResult maybeWhen<TResult extends Object?>({ + TResult Function(String? email)? emailChanged, + TResult Function(String? password)? passwordChanged, + TResult Function()? signUpWithEmailAndPasswordPressed, + TResult Function()? signInWithEmailAndPasswordPressed, required TResult orElse(), }) { - if (emailChanged != null) { - return emailChanged(this); + final _that = this; + switch (_that) { + case EmailChanged() when emailChanged != null: + return emailChanged(_that.email); + case PasswordChanged() when passwordChanged != null: + return passwordChanged(_that.password); + case SignUPWithEmailAndPasswordPressed() + when signUpWithEmailAndPasswordPressed != null: + return signUpWithEmailAndPasswordPressed(); + case SignInWithEmailAndPasswordPressed() + when signInWithEmailAndPasswordPressed != null: + return signInWithEmailAndPasswordPressed(); + case _: + return orElse(); } - return orElse(); - } -} - -abstract class EmailChanged implements AuthEvents { - const factory EmailChanged({required final String? email}) = - _$EmailChangedImpl; - - String? get email; - @JsonKey(ignore: true) - _$$EmailChangedImplCopyWith<_$EmailChangedImpl> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$PasswordChangedImplCopyWith<$Res> { - factory _$$PasswordChangedImplCopyWith(_$PasswordChangedImpl value, - $Res Function(_$PasswordChangedImpl) then) = - __$$PasswordChangedImplCopyWithImpl<$Res>; - @useResult - $Res call({String? password}); -} - -/// @nodoc -class __$$PasswordChangedImplCopyWithImpl<$Res> - extends _$AuthEventsCopyWithImpl<$Res, _$PasswordChangedImpl> - implements _$$PasswordChangedImplCopyWith<$Res> { - __$$PasswordChangedImplCopyWithImpl( - _$PasswordChangedImpl _value, $Res Function(_$PasswordChangedImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? password = freezed, - }) { - return _then(_$PasswordChangedImpl( - password: freezed == password - ? _value.password - : password // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$PasswordChangedImpl implements PasswordChanged { - const _$PasswordChangedImpl({required this.password}); - - @override - final String? password; - - @override - String toString() { - return 'AuthEvents.passwordChanged(password: $password)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PasswordChangedImpl && - (identical(other.password, password) || - other.password == password)); } - @override - int get hashCode => Object.hash(runtimeType, password); + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => - __$$PasswordChangedImplCopyWithImpl<_$PasswordChangedImpl>( - this, _$identity); - - @override @optionalTypeArgs TResult when<TResult extends Object?>({ required TResult Function(String? email) emailChanged, @@ -315,10 +207,33 @@ class _$PasswordChangedImpl implements PasswordChanged { required TResult Function() signUpWithEmailAndPasswordPressed, required TResult Function() signInWithEmailAndPasswordPressed, }) { - return passwordChanged(password); + final _that = this; + switch (_that) { + case EmailChanged(): + return emailChanged(_that.email); + case PasswordChanged(): + return passwordChanged(_that.password); + case SignUPWithEmailAndPasswordPressed(): + return signUpWithEmailAndPasswordPressed(); + case SignInWithEmailAndPasswordPressed(): + return signInWithEmailAndPasswordPressed(); + case _: + throw StateError('Unexpected subclass'); + } } - @override + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({ TResult? Function(String? email)? emailChanged, @@ -326,326 +241,192 @@ class _$PasswordChangedImpl implements PasswordChanged { TResult? Function()? signUpWithEmailAndPasswordPressed, TResult? Function()? signInWithEmailAndPasswordPressed, }) { - return passwordChanged?.call(password); - } - - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? email)? emailChanged, - TResult Function(String? password)? passwordChanged, - TResult Function()? signUpWithEmailAndPasswordPressed, - TResult Function()? signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (passwordChanged != null) { - return passwordChanged(password); + final _that = this; + switch (_that) { + case EmailChanged() when emailChanged != null: + return emailChanged(_that.email); + case PasswordChanged() when passwordChanged != null: + return passwordChanged(_that.password); + case SignUPWithEmailAndPasswordPressed() + when signUpWithEmailAndPasswordPressed != null: + return signUpWithEmailAndPasswordPressed(); + case SignInWithEmailAndPasswordPressed() + when signInWithEmailAndPasswordPressed != null: + return signInWithEmailAndPasswordPressed(); + case _: + return null; } - return orElse(); } +} + +/// @nodoc + +class EmailChanged implements AuthEvents { + const EmailChanged({required this.email}); + + final String? email; + + /// Create a copy of AuthEvents + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $EmailChangedCopyWith<EmailChanged> get copyWith => + _$EmailChangedCopyWithImpl<EmailChanged>(this, _$identity); @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(EmailChanged value) emailChanged, - required TResult Function(PasswordChanged value) passwordChanged, - required TResult Function(SignUPWithEmailAndPasswordPressed value) - signUpWithEmailAndPasswordPressed, - required TResult Function(SignInWithEmailAndPasswordPressed value) - signInWithEmailAndPasswordPressed, - }) { - return passwordChanged(this); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is EmailChanged && + (identical(other.email, email) || other.email == email)); } @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(EmailChanged value)? emailChanged, - TResult? Function(PasswordChanged value)? passwordChanged, - TResult? Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult? Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - }) { - return passwordChanged?.call(this); - } + int get hashCode => Object.hash(runtimeType, email); @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(EmailChanged value)? emailChanged, - TResult Function(PasswordChanged value)? passwordChanged, - TResult Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (passwordChanged != null) { - return passwordChanged(this); - } - return orElse(); + String toString() { + return 'AuthEvents.emailChanged(email: $email)'; } } -abstract class PasswordChanged implements AuthEvents { - const factory PasswordChanged({required final String? password}) = - _$PasswordChangedImpl; - - String? get password; - @JsonKey(ignore: true) - _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => - throw _privateConstructorUsedError; -} - /// @nodoc -abstract class _$$SignUPWithEmailAndPasswordPressedImplCopyWith<$Res> { - factory _$$SignUPWithEmailAndPasswordPressedImplCopyWith( - _$SignUPWithEmailAndPasswordPressedImpl value, - $Res Function(_$SignUPWithEmailAndPasswordPressedImpl) then) = - __$$SignUPWithEmailAndPasswordPressedImplCopyWithImpl<$Res>; +abstract mixin class $EmailChangedCopyWith<$Res> + implements $AuthEventsCopyWith<$Res> { + factory $EmailChangedCopyWith( + EmailChanged value, $Res Function(EmailChanged) _then) = + _$EmailChangedCopyWithImpl; + @useResult + $Res call({String? email}); } /// @nodoc -class __$$SignUPWithEmailAndPasswordPressedImplCopyWithImpl<$Res> - extends _$AuthEventsCopyWithImpl<$Res, - _$SignUPWithEmailAndPasswordPressedImpl> - implements _$$SignUPWithEmailAndPasswordPressedImplCopyWith<$Res> { - __$$SignUPWithEmailAndPasswordPressedImplCopyWithImpl( - _$SignUPWithEmailAndPasswordPressedImpl _value, - $Res Function(_$SignUPWithEmailAndPasswordPressedImpl) _then) - : super(_value, _then); +class _$EmailChangedCopyWithImpl<$Res> implements $EmailChangedCopyWith<$Res> { + _$EmailChangedCopyWithImpl(this._self, this._then); + + final EmailChanged _self; + final $Res Function(EmailChanged) _then; + + /// Create a copy of AuthEvents + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + $Res call({ + Object? email = freezed, + }) { + return _then(EmailChanged( + email: freezed == email + ? _self.email + : email // ignore: cast_nullable_to_non_nullable + as String?, + )); + } } /// @nodoc -class _$SignUPWithEmailAndPasswordPressedImpl - implements SignUPWithEmailAndPasswordPressed { - const _$SignUPWithEmailAndPasswordPressedImpl(); +class PasswordChanged implements AuthEvents { + const PasswordChanged({required this.password}); - @override - String toString() { - return 'AuthEvents.signUpWithEmailAndPasswordPressed()'; - } + final String? password; + + /// Create a copy of AuthEvents + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $PasswordChangedCopyWith<PasswordChanged> get copyWith => + _$PasswordChangedCopyWithImpl<PasswordChanged>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SignUPWithEmailAndPasswordPressedImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? email) emailChanged, - required TResult Function(String? password) passwordChanged, - required TResult Function() signUpWithEmailAndPasswordPressed, - required TResult Function() signInWithEmailAndPasswordPressed, - }) { - return signUpWithEmailAndPasswordPressed(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? email)? emailChanged, - TResult? Function(String? password)? passwordChanged, - TResult? Function()? signUpWithEmailAndPasswordPressed, - TResult? Function()? signInWithEmailAndPasswordPressed, - }) { - return signUpWithEmailAndPasswordPressed?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? email)? emailChanged, - TResult Function(String? password)? passwordChanged, - TResult Function()? signUpWithEmailAndPasswordPressed, - TResult Function()? signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (signUpWithEmailAndPasswordPressed != null) { - return signUpWithEmailAndPasswordPressed(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(EmailChanged value) emailChanged, - required TResult Function(PasswordChanged value) passwordChanged, - required TResult Function(SignUPWithEmailAndPasswordPressed value) - signUpWithEmailAndPasswordPressed, - required TResult Function(SignInWithEmailAndPasswordPressed value) - signInWithEmailAndPasswordPressed, - }) { - return signUpWithEmailAndPasswordPressed(this); + other is PasswordChanged && + (identical(other.password, password) || + other.password == password)); } @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(EmailChanged value)? emailChanged, - TResult? Function(PasswordChanged value)? passwordChanged, - TResult? Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult? Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - }) { - return signUpWithEmailAndPasswordPressed?.call(this); - } + int get hashCode => Object.hash(runtimeType, password); @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(EmailChanged value)? emailChanged, - TResult Function(PasswordChanged value)? passwordChanged, - TResult Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (signUpWithEmailAndPasswordPressed != null) { - return signUpWithEmailAndPasswordPressed(this); - } - return orElse(); + String toString() { + return 'AuthEvents.passwordChanged(password: $password)'; } } -abstract class SignUPWithEmailAndPasswordPressed implements AuthEvents { - const factory SignUPWithEmailAndPasswordPressed() = - _$SignUPWithEmailAndPasswordPressedImpl; -} - /// @nodoc -abstract class _$$SignInWithEmailAndPasswordPressedImplCopyWith<$Res> { - factory _$$SignInWithEmailAndPasswordPressedImplCopyWith( - _$SignInWithEmailAndPasswordPressedImpl value, - $Res Function(_$SignInWithEmailAndPasswordPressedImpl) then) = - __$$SignInWithEmailAndPasswordPressedImplCopyWithImpl<$Res>; +abstract mixin class $PasswordChangedCopyWith<$Res> + implements $AuthEventsCopyWith<$Res> { + factory $PasswordChangedCopyWith( + PasswordChanged value, $Res Function(PasswordChanged) _then) = + _$PasswordChangedCopyWithImpl; + @useResult + $Res call({String? password}); } /// @nodoc -class __$$SignInWithEmailAndPasswordPressedImplCopyWithImpl<$Res> - extends _$AuthEventsCopyWithImpl<$Res, - _$SignInWithEmailAndPasswordPressedImpl> - implements _$$SignInWithEmailAndPasswordPressedImplCopyWith<$Res> { - __$$SignInWithEmailAndPasswordPressedImplCopyWithImpl( - _$SignInWithEmailAndPasswordPressedImpl _value, - $Res Function(_$SignInWithEmailAndPasswordPressedImpl) _then) - : super(_value, _then); +class _$PasswordChangedCopyWithImpl<$Res> + implements $PasswordChangedCopyWith<$Res> { + _$PasswordChangedCopyWithImpl(this._self, this._then); + + final PasswordChanged _self; + final $Res Function(PasswordChanged) _then; + + /// Create a copy of AuthEvents + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + $Res call({ + Object? password = freezed, + }) { + return _then(PasswordChanged( + password: freezed == password + ? _self.password + : password // ignore: cast_nullable_to_non_nullable + as String?, + )); + } } /// @nodoc -class _$SignInWithEmailAndPasswordPressedImpl - implements SignInWithEmailAndPasswordPressed { - const _$SignInWithEmailAndPasswordPressedImpl(); - - @override - String toString() { - return 'AuthEvents.signInWithEmailAndPasswordPressed()'; - } +class SignUPWithEmailAndPasswordPressed implements AuthEvents { + const SignUPWithEmailAndPasswordPressed(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SignInWithEmailAndPasswordPressedImpl); + other is SignUPWithEmailAndPasswordPressed); } @override int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? email) emailChanged, - required TResult Function(String? password) passwordChanged, - required TResult Function() signUpWithEmailAndPasswordPressed, - required TResult Function() signInWithEmailAndPasswordPressed, - }) { - return signInWithEmailAndPasswordPressed(); + String toString() { + return 'AuthEvents.signUpWithEmailAndPasswordPressed()'; } +} - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? email)? emailChanged, - TResult? Function(String? password)? passwordChanged, - TResult? Function()? signUpWithEmailAndPasswordPressed, - TResult? Function()? signInWithEmailAndPasswordPressed, - }) { - return signInWithEmailAndPasswordPressed?.call(); - } +/// @nodoc - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? email)? emailChanged, - TResult Function(String? password)? passwordChanged, - TResult Function()? signUpWithEmailAndPasswordPressed, - TResult Function()? signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (signInWithEmailAndPasswordPressed != null) { - return signInWithEmailAndPasswordPressed(); - } - return orElse(); - } +class SignInWithEmailAndPasswordPressed implements AuthEvents { + const SignInWithEmailAndPasswordPressed(); @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(EmailChanged value) emailChanged, - required TResult Function(PasswordChanged value) passwordChanged, - required TResult Function(SignUPWithEmailAndPasswordPressed value) - signUpWithEmailAndPasswordPressed, - required TResult Function(SignInWithEmailAndPasswordPressed value) - signInWithEmailAndPasswordPressed, - }) { - return signInWithEmailAndPasswordPressed(this); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is SignInWithEmailAndPasswordPressed); } @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(EmailChanged value)? emailChanged, - TResult? Function(PasswordChanged value)? passwordChanged, - TResult? Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult? Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - }) { - return signInWithEmailAndPasswordPressed?.call(this); - } + int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(EmailChanged value)? emailChanged, - TResult Function(PasswordChanged value)? passwordChanged, - TResult Function(SignUPWithEmailAndPasswordPressed value)? - signUpWithEmailAndPasswordPressed, - TResult Function(SignInWithEmailAndPasswordPressed value)? - signInWithEmailAndPasswordPressed, - required TResult orElse(), - }) { - if (signInWithEmailAndPasswordPressed != null) { - return signInWithEmailAndPasswordPressed(this); - } - return orElse(); + String toString() { + return 'AuthEvents.signInWithEmailAndPasswordPressed()'; } } -abstract class SignInWithEmailAndPasswordPressed implements AuthEvents { - const factory SignInWithEmailAndPasswordPressed() = - _$SignInWithEmailAndPasswordPressedImpl; -} +// dart format on diff --git a/lib/application/authentication/auth_state_controller.dart b/lib/application/authentication/auth_state_controller.dart index 92f56b9..bebfd99 100644 --- a/lib/application/authentication/auth_state_controller.dart +++ b/lib/application/authentication/auth_state_controller.dart @@ -1,26 +1,29 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_failures.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:fpdart/fpdart.dart"; -import "../../Domain/Authentication/auth_value_objects.dart"; -import "../../Domain/Authentication/i_auth_facade.dart"; +import "../../domain/authentication/auth_value_objects.dart"; +import "../../domain/authentication/i_auth_facade.dart"; +import "../../services/authentication/firebase_auth_facade.dart"; import "auth_events.dart"; import "auth_states.dart"; -class AuthStateController extends StateNotifier<AuthStates> { - AuthStateController(this._authFacade) : super(AuthStates.initial()); +class AuthStateController extends Notifier<AuthStates> { + @override + AuthStates build() { + return AuthStates.initial(); + } - final IAuthFacade _authFacade; + IAuthFacade get _authFacade => ref.read(firebaseAuthFacadeProvider); - Future mapEventsToStates(AuthEvents events) async { - return events.map( + Future<void> mapEventsToStates(AuthEvents events) async { + await events.map( emailChanged: (value) async { state = state.copyWith( emailAddress: EmailAddress( email: value.email, ), authFailureOrSuccess: none()); - return null; }, passwordChanged: (value) async { state = state.copyWith( @@ -29,24 +32,47 @@ class AuthStateController extends StateNotifier<AuthStates> { ), authFailureOrSuccess: none(), ); - return null; }, signUpWithEmailAndPasswordPressed: (value) async { await _performAuthAction( _authFacade.registerWithEmailAndPassword, ); - return null; }, signInWithEmailAndPasswordPressed: (value) async { await _performAuthAction( _authFacade.signInWithEmailAndPassword, ); - return null; }, ); } - Future _performAuthAction( + void emailChanged(String email) { + state = state.copyWith( + emailAddress: EmailAddress(email: email), + authFailureOrSuccess: none(), + ); + } + + void passwordChanged(String password) { + state = state.copyWith( + password: Password(password: password), + authFailureOrSuccess: none(), + ); + } + + Future<void> signUpWithEmailAndPassword() async { + await _performAuthAction( + _authFacade.registerWithEmailAndPassword, + ); + } + + Future<void> signInWithEmailAndPassword() async { + await _performAuthAction( + _authFacade.signInWithEmailAndPassword, + ); + } + + Future<void> _performAuthAction( Future<Either<AuthFailures, Unit>> Function( {required EmailAddress emailAddress, required Password password}) forwardCall, @@ -54,16 +80,19 @@ class AuthStateController extends StateNotifier<AuthStates> { final isEmailValid = state.emailAddress.isValid(); final isPasswordValid = state.password.isValid(); Either<AuthFailures, Unit>? failureOrSuccess; + if (isEmailValid && isPasswordValid) { state = state.copyWith( isSubmitting: true, authFailureOrSuccess: none(), ); + failureOrSuccess = await forwardCall( emailAddress: state.emailAddress, password: state.password, ); } + state = state.copyWith( isSubmitting: false, showError: true, @@ -71,3 +100,7 @@ class AuthStateController extends StateNotifier<AuthStates> { ); } } + +final authStateControllerProvider = NotifierProvider<AuthStateController, AuthStates>(() { + return AuthStateController(); +}); diff --git a/lib/application/authentication/auth_states.dart b/lib/application/authentication/auth_states.dart index 2d8bcb6..023784b 100644 --- a/lib/application/authentication/auth_states.dart +++ b/lib/application/authentication/auth_states.dart @@ -1,13 +1,13 @@ import "package:fpdart/fpdart.dart"; import "package:freezed_annotation/freezed_annotation.dart"; -import "../../Domain/Authentication/auth_failures.dart"; -import "../../Domain/Authentication/auth_value_objects.dart"; +import "../../domain/authentication/auth_failures.dart"; +import "../../domain/authentication/auth_value_objects.dart"; part "auth_states.freezed.dart"; @freezed -class AuthStates with _$AuthStates { +sealed class AuthStates with _$AuthStates { const factory AuthStates({ required EmailAddress emailAddress, required Password password, diff --git a/lib/application/authentication/auth_states.freezed.dart b/lib/application/authentication/auth_states.freezed.dart index 170f1d8..2a5a006 100644 --- a/lib/application/authentication/auth_states.freezed.dart +++ b/lib/application/authentication/auth_states.freezed.dart @@ -1,5 +1,5 @@ -// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file // ignore_for_file: type=lint // 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 @@ -9,30 +9,56 @@ part of 'auth_states.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity<T>(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$AuthStates { - EmailAddress get emailAddress => throw _privateConstructorUsedError; - Password get password => throw _privateConstructorUsedError; - bool get isSubmitting => throw _privateConstructorUsedError; - bool get showError => throw _privateConstructorUsedError; - Option<Either<AuthFailures, Unit>> get authFailureOrSuccess => - throw _privateConstructorUsedError; - - @JsonKey(ignore: true) + EmailAddress get emailAddress; + Password get password; + bool get isSubmitting; + bool get showError; + Option<Either<AuthFailures, Unit>> get authFailureOrSuccess; + + /// Create a copy of AuthStates + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $AuthStatesCopyWith<AuthStates> get copyWith => - throw _privateConstructorUsedError; + _$AuthStatesCopyWithImpl<AuthStates>(this as AuthStates, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is AuthStates && + (identical(other.emailAddress, emailAddress) || + other.emailAddress == emailAddress) && + (identical(other.password, password) || + other.password == password) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showError, showError) || + other.showError == showError) && + (identical(other.authFailureOrSuccess, authFailureOrSuccess) || + other.authFailureOrSuccess == authFailureOrSuccess)); + } + + @override + int get hashCode => Object.hash(runtimeType, emailAddress, password, + isSubmitting, showError, authFailureOrSuccess); + + @override + String toString() { + return 'AuthStates(emailAddress: $emailAddress, password: $password, isSubmitting: $isSubmitting, showError: $showError, authFailureOrSuccess: $authFailureOrSuccess)'; + } } /// @nodoc -abstract class $AuthStatesCopyWith<$Res> { +abstract mixin class $AuthStatesCopyWith<$Res> { factory $AuthStatesCopyWith( - AuthStates value, $Res Function(AuthStates) then) = - _$AuthStatesCopyWithImpl<$Res, AuthStates>; + AuthStates value, $Res Function(AuthStates) _then) = + _$AuthStatesCopyWithImpl; @useResult $Res call( {EmailAddress emailAddress, @@ -43,111 +69,226 @@ abstract class $AuthStatesCopyWith<$Res> { } /// @nodoc -class _$AuthStatesCopyWithImpl<$Res, $Val extends AuthStates> - implements $AuthStatesCopyWith<$Res> { - _$AuthStatesCopyWithImpl(this._value, this._then); +class _$AuthStatesCopyWithImpl<$Res> implements $AuthStatesCopyWith<$Res> { + _$AuthStatesCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final AuthStates _self; + final $Res Function(AuthStates) _then; + /// Create a copy of AuthStates + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? emailAddress = freezed, - Object? password = freezed, + Object? emailAddress = null, + Object? password = null, Object? isSubmitting = null, Object? showError = null, Object? authFailureOrSuccess = null, }) { - return _then(_value.copyWith( - emailAddress: freezed == emailAddress - ? _value.emailAddress + return _then(_self.copyWith( + emailAddress: null == emailAddress + ? _self.emailAddress : emailAddress // ignore: cast_nullable_to_non_nullable as EmailAddress, - password: freezed == password - ? _value.password + password: null == password + ? _self.password : password // ignore: cast_nullable_to_non_nullable as Password, isSubmitting: null == isSubmitting - ? _value.isSubmitting + ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable as bool, showError: null == showError - ? _value.showError + ? _self.showError : showError // ignore: cast_nullable_to_non_nullable as bool, authFailureOrSuccess: null == authFailureOrSuccess - ? _value.authFailureOrSuccess + ? _self.authFailureOrSuccess : authFailureOrSuccess // ignore: cast_nullable_to_non_nullable as Option<Either<AuthFailures, Unit>>, - ) as $Val); + )); } } -/// @nodoc -abstract class _$$AuthStatesImplCopyWith<$Res> - implements $AuthStatesCopyWith<$Res> { - factory _$$AuthStatesImplCopyWith( - _$AuthStatesImpl value, $Res Function(_$AuthStatesImpl) then) = - __$$AuthStatesImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {EmailAddress emailAddress, - Password password, - bool isSubmitting, - bool showError, - Option<Either<AuthFailures, Unit>> authFailureOrSuccess}); -} +/// Adds pattern-matching-related methods to [AuthStates]. +extension AuthStatesPatterns on AuthStates { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` -/// @nodoc -class __$$AuthStatesImplCopyWithImpl<$Res> - extends _$AuthStatesCopyWithImpl<$Res, _$AuthStatesImpl> - implements _$$AuthStatesImplCopyWith<$Res> { - __$$AuthStatesImplCopyWithImpl( - _$AuthStatesImpl _value, $Res Function(_$AuthStatesImpl) _then) - : super(_value, _then); + @optionalTypeArgs + TResult maybeMap<TResult extends Object?>( + TResult Function(_AuthStates value)? $default, { + required TResult orElse(), + }) { + final _that = this; + switch (_that) { + case _AuthStates() when $default != null: + return $default(_that); + case _: + return orElse(); + } + } - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? emailAddress = freezed, - Object? password = freezed, - Object? isSubmitting = null, - Object? showError = null, - Object? authFailureOrSuccess = null, + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult map<TResult extends Object?>( + TResult Function(_AuthStates value) $default, + ) { + final _that = this; + switch (_that) { + case _AuthStates(): + return $default(_that); + } + } + + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? mapOrNull<TResult extends Object?>( + TResult? Function(_AuthStates value)? $default, + ) { + final _that = this; + switch (_that) { + case _AuthStates() when $default != null: + return $default(_that); + case _: + return null; + } + } + + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + + @optionalTypeArgs + TResult maybeWhen<TResult extends Object?>( + TResult Function( + EmailAddress emailAddress, + Password password, + bool isSubmitting, + bool showError, + Option<Either<AuthFailures, Unit>> authFailureOrSuccess)? + $default, { + required TResult orElse(), }) { - return _then(_$AuthStatesImpl( - emailAddress: freezed == emailAddress - ? _value.emailAddress - : emailAddress // ignore: cast_nullable_to_non_nullable - as EmailAddress, - password: freezed == password - ? _value.password - : password // ignore: cast_nullable_to_non_nullable - as Password, - isSubmitting: null == isSubmitting - ? _value.isSubmitting - : isSubmitting // ignore: cast_nullable_to_non_nullable - as bool, - showError: null == showError - ? _value.showError - : showError // ignore: cast_nullable_to_non_nullable - as bool, - authFailureOrSuccess: null == authFailureOrSuccess - ? _value.authFailureOrSuccess - : authFailureOrSuccess // ignore: cast_nullable_to_non_nullable - as Option<Either<AuthFailures, Unit>>, - )); + final _that = this; + switch (_that) { + case _AuthStates() when $default != null: + return $default(_that.emailAddress, _that.password, _that.isSubmitting, + _that.showError, _that.authFailureOrSuccess); + case _: + return orElse(); + } + } + + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` + + @optionalTypeArgs + TResult when<TResult extends Object?>( + TResult Function( + EmailAddress emailAddress, + Password password, + bool isSubmitting, + bool showError, + Option<Either<AuthFailures, Unit>> authFailureOrSuccess) + $default, + ) { + final _that = this; + switch (_that) { + case _AuthStates(): + return $default(_that.emailAddress, _that.password, _that.isSubmitting, + _that.showError, _that.authFailureOrSuccess); + } + } + + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + + @optionalTypeArgs + TResult? whenOrNull<TResult extends Object?>( + TResult? Function( + EmailAddress emailAddress, + Password password, + bool isSubmitting, + bool showError, + Option<Either<AuthFailures, Unit>> authFailureOrSuccess)? + $default, + ) { + final _that = this; + switch (_that) { + case _AuthStates() when $default != null: + return $default(_that.emailAddress, _that.password, _that.isSubmitting, + _that.showError, _that.authFailureOrSuccess); + case _: + return null; + } } } /// @nodoc -class _$AuthStatesImpl implements _AuthStates { - const _$AuthStatesImpl( +class _AuthStates implements AuthStates { + const _AuthStates( {required this.emailAddress, required this.password, required this.isSubmitting, @@ -165,19 +306,23 @@ class _$AuthStatesImpl implements _AuthStates { @override final Option<Either<AuthFailures, Unit>> authFailureOrSuccess; + /// Create a copy of AuthStates + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'AuthStates(emailAddress: $emailAddress, password: $password, isSubmitting: $isSubmitting, showError: $showError, authFailureOrSuccess: $authFailureOrSuccess)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$AuthStatesCopyWith<_AuthStates> get copyWith => + __$AuthStatesCopyWithImpl<_AuthStates>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$AuthStatesImpl && - const DeepCollectionEquality() - .equals(other.emailAddress, emailAddress) && - const DeepCollectionEquality().equals(other.password, password) && + other is _AuthStates && + (identical(other.emailAddress, emailAddress) || + other.emailAddress == emailAddress) && + (identical(other.password, password) || + other.password == password) && (identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting) && (identical(other.showError, showError) || @@ -187,42 +332,72 @@ class _$AuthStatesImpl implements _AuthStates { } @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(emailAddress), - const DeepCollectionEquality().hash(password), - isSubmitting, - showError, - authFailureOrSuccess); - - @JsonKey(ignore: true) + int get hashCode => Object.hash(runtimeType, emailAddress, password, + isSubmitting, showError, authFailureOrSuccess); + @override - @pragma('vm:prefer-inline') - _$$AuthStatesImplCopyWith<_$AuthStatesImpl> get copyWith => - __$$AuthStatesImplCopyWithImpl<_$AuthStatesImpl>(this, _$identity); + String toString() { + return 'AuthStates(emailAddress: $emailAddress, password: $password, isSubmitting: $isSubmitting, showError: $showError, authFailureOrSuccess: $authFailureOrSuccess)'; + } } -abstract class _AuthStates implements AuthStates { - const factory _AuthStates( - {required final EmailAddress emailAddress, - required final Password password, - required final bool isSubmitting, - required final bool showError, - required final Option<Either<AuthFailures, Unit>> - authFailureOrSuccess}) = _$AuthStatesImpl; - - @override - EmailAddress get emailAddress; - @override - Password get password; - @override - bool get isSubmitting; - @override - bool get showError; +/// @nodoc +abstract mixin class _$AuthStatesCopyWith<$Res> + implements $AuthStatesCopyWith<$Res> { + factory _$AuthStatesCopyWith( + _AuthStates value, $Res Function(_AuthStates) _then) = + __$AuthStatesCopyWithImpl; @override - Option<Either<AuthFailures, Unit>> get authFailureOrSuccess; + @useResult + $Res call( + {EmailAddress emailAddress, + Password password, + bool isSubmitting, + bool showError, + Option<Either<AuthFailures, Unit>> authFailureOrSuccess}); +} + +/// @nodoc +class __$AuthStatesCopyWithImpl<$Res> implements _$AuthStatesCopyWith<$Res> { + __$AuthStatesCopyWithImpl(this._self, this._then); + + final _AuthStates _self; + final $Res Function(_AuthStates) _then; + + /// Create a copy of AuthStates + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) - _$$AuthStatesImplCopyWith<_$AuthStatesImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? emailAddress = null, + Object? password = null, + Object? isSubmitting = null, + Object? showError = null, + Object? authFailureOrSuccess = null, + }) { + return _then(_AuthStates( + emailAddress: null == emailAddress + ? _self.emailAddress + : emailAddress // ignore: cast_nullable_to_non_nullable + as EmailAddress, + password: null == password + ? _self.password + : password // ignore: cast_nullable_to_non_nullable + as Password, + isSubmitting: null == isSubmitting + ? _self.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showError: null == showError + ? _self.showError + : showError // ignore: cast_nullable_to_non_nullable + as bool, + authFailureOrSuccess: null == authFailureOrSuccess + ? _self.authFailureOrSuccess + : authFailureOrSuccess // ignore: cast_nullable_to_non_nullable + as Option<Either<AuthFailures, Unit>>, + )); + } } + +// dart format on diff --git a/lib/domain/authentication/auth_failures.freezed.dart b/lib/domain/authentication/auth_failures.freezed.dart index 6e1bba8..e23f700 100644 --- a/lib/domain/authentication/auth_failures.freezed.dart +++ b/lib/domain/authentication/auth_failures.freezed.dart @@ -1,5 +1,5 @@ -// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file // ignore_for_file: type=lint // 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 @@ -9,150 +9,80 @@ part of 'auth_failures.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity<T>(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$AuthFailures { - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function() serverError, - required TResult Function() emailAlreadyInUse, - required TResult Function() invalidEmailAndPasswordCombination, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function()? serverError, - TResult? Function()? emailAlreadyInUse, - TResult? Function()? invalidEmailAndPasswordCombination, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function()? serverError, - TResult Function()? emailAlreadyInUse, - TResult Function()? invalidEmailAndPasswordCombination, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(ServerError value) serverError, - required TResult Function(EmailAlreadyInUse value) emailAlreadyInUse, - required TResult Function(InavalidEmailAndPasswordCombination value) - invalidEmailAndPasswordCombination, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(ServerError value)? serverError, - TResult? Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult? Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(ServerError value)? serverError, - TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $AuthFailuresCopyWith<$Res> { - factory $AuthFailuresCopyWith( - AuthFailures value, $Res Function(AuthFailures) then) = - _$AuthFailuresCopyWithImpl<$Res, AuthFailures>; -} - -/// @nodoc -class _$AuthFailuresCopyWithImpl<$Res, $Val extends AuthFailures> - implements $AuthFailuresCopyWith<$Res> { - _$AuthFailuresCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; -} - -/// @nodoc -abstract class _$$ServerErrorImplCopyWith<$Res> { - factory _$$ServerErrorImplCopyWith( - _$ServerErrorImpl value, $Res Function(_$ServerErrorImpl) then) = - __$$ServerErrorImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$ServerErrorImplCopyWithImpl<$Res> - extends _$AuthFailuresCopyWithImpl<$Res, _$ServerErrorImpl> - implements _$$ServerErrorImplCopyWith<$Res> { - __$$ServerErrorImplCopyWithImpl( - _$ServerErrorImpl _value, $Res Function(_$ServerErrorImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$ServerErrorImpl implements ServerError { - const _$ServerErrorImpl(); - - @override - String toString() { - return 'AuthFailures.serverError()'; - } - @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && other is _$ServerErrorImpl); + (other.runtimeType == runtimeType && other is AuthFailures); } @override int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function() serverError, - required TResult Function() emailAlreadyInUse, - required TResult Function() invalidEmailAndPasswordCombination, - }) { - return serverError(); + String toString() { + return 'AuthFailures()'; } +} - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function()? serverError, - TResult? Function()? emailAlreadyInUse, - TResult? Function()? invalidEmailAndPasswordCombination, - }) { - return serverError?.call(); - } +/// @nodoc +class $AuthFailuresCopyWith<$Res> { + $AuthFailuresCopyWith(AuthFailures _, $Res Function(AuthFailures) __); +} + +/// Adds pattern-matching-related methods to [AuthFailures]. +extension AuthFailuresPatterns on AuthFailures { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` - @override @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function()? serverError, - TResult Function()? emailAlreadyInUse, - TResult Function()? invalidEmailAndPasswordCombination, + TResult maybeMap<TResult extends Object?>({ + TResult Function(ServerError value)? serverError, + TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse, + TResult Function(InavalidEmailAndPasswordCombination value)? + invalidEmailAndPasswordCombination, required TResult orElse(), }) { - if (serverError != null) { - return serverError(); + final _that = this; + switch (_that) { + case ServerError() when serverError != null: + return serverError(_that); + case EmailAlreadyInUse() when emailAlreadyInUse != null: + return emailAlreadyInUse(_that); + case InavalidEmailAndPasswordCombination() + when invalidEmailAndPasswordCombination != null: + return invalidEmailAndPasswordCombination(_that); + case _: + return orElse(); } - return orElse(); } - @override + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + @optionalTypeArgs TResult map<TResult extends Object?>({ required TResult Function(ServerError value) serverError, @@ -160,10 +90,31 @@ class _$ServerErrorImpl implements ServerError { required TResult Function(InavalidEmailAndPasswordCombination value) invalidEmailAndPasswordCombination, }) { - return serverError(this); + final _that = this; + switch (_that) { + case ServerError(): + return serverError(_that); + case EmailAlreadyInUse(): + return emailAlreadyInUse(_that); + case InavalidEmailAndPasswordCombination(): + return invalidEmailAndPasswordCombination(_that); + case _: + throw StateError('Unexpected subclass'); + } } - @override + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({ TResult? Function(ServerError value)? serverError, @@ -171,253 +122,177 @@ class _$ServerErrorImpl implements ServerError { TResult? Function(InavalidEmailAndPasswordCombination value)? invalidEmailAndPasswordCombination, }) { - return serverError?.call(this); + final _that = this; + switch (_that) { + case ServerError() when serverError != null: + return serverError(_that); + case EmailAlreadyInUse() when emailAlreadyInUse != null: + return emailAlreadyInUse(_that); + case InavalidEmailAndPasswordCombination() + when invalidEmailAndPasswordCombination != null: + return invalidEmailAndPasswordCombination(_that); + case _: + return null; + } } - @override + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` + @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(ServerError value)? serverError, - TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, + TResult maybeWhen<TResult extends Object?>({ + TResult Function()? serverError, + TResult Function()? emailAlreadyInUse, + TResult Function()? invalidEmailAndPasswordCombination, required TResult orElse(), }) { - if (serverError != null) { - return serverError(this); + final _that = this; + switch (_that) { + case ServerError() when serverError != null: + return serverError(); + case EmailAlreadyInUse() when emailAlreadyInUse != null: + return emailAlreadyInUse(); + case InavalidEmailAndPasswordCombination() + when invalidEmailAndPasswordCombination != null: + return invalidEmailAndPasswordCombination(); + case _: + return orElse(); } - return orElse(); - } -} - -abstract class ServerError implements AuthFailures { - const factory ServerError() = _$ServerErrorImpl; -} - -/// @nodoc -abstract class _$$EmailAlreadyInUseImplCopyWith<$Res> { - factory _$$EmailAlreadyInUseImplCopyWith(_$EmailAlreadyInUseImpl value, - $Res Function(_$EmailAlreadyInUseImpl) then) = - __$$EmailAlreadyInUseImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$EmailAlreadyInUseImplCopyWithImpl<$Res> - extends _$AuthFailuresCopyWithImpl<$Res, _$EmailAlreadyInUseImpl> - implements _$$EmailAlreadyInUseImplCopyWith<$Res> { - __$$EmailAlreadyInUseImplCopyWithImpl(_$EmailAlreadyInUseImpl _value, - $Res Function(_$EmailAlreadyInUseImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$EmailAlreadyInUseImpl implements EmailAlreadyInUse { - const _$EmailAlreadyInUseImpl(); - - @override - String toString() { - return 'AuthFailures.emailAlreadyInUse()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$EmailAlreadyInUseImpl); } - @override - int get hashCode => runtimeType.hashCode; + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` - @override @optionalTypeArgs TResult when<TResult extends Object?>({ required TResult Function() serverError, required TResult Function() emailAlreadyInUse, required TResult Function() invalidEmailAndPasswordCombination, }) { - return emailAlreadyInUse(); + final _that = this; + switch (_that) { + case ServerError(): + return serverError(); + case EmailAlreadyInUse(): + return emailAlreadyInUse(); + case InavalidEmailAndPasswordCombination(): + return invalidEmailAndPasswordCombination(); + case _: + throw StateError('Unexpected subclass'); + } } - @override + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({ TResult? Function()? serverError, TResult? Function()? emailAlreadyInUse, TResult? Function()? invalidEmailAndPasswordCombination, }) { - return emailAlreadyInUse?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function()? serverError, - TResult Function()? emailAlreadyInUse, - TResult Function()? invalidEmailAndPasswordCombination, - required TResult orElse(), - }) { - if (emailAlreadyInUse != null) { - return emailAlreadyInUse(); + final _that = this; + switch (_that) { + case ServerError() when serverError != null: + return serverError(); + case EmailAlreadyInUse() when emailAlreadyInUse != null: + return emailAlreadyInUse(); + case InavalidEmailAndPasswordCombination() + when invalidEmailAndPasswordCombination != null: + return invalidEmailAndPasswordCombination(); + case _: + return null; } - return orElse(); } +} + +/// @nodoc + +class ServerError implements AuthFailures { + const ServerError(); @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(ServerError value) serverError, - required TResult Function(EmailAlreadyInUse value) emailAlreadyInUse, - required TResult Function(InavalidEmailAndPasswordCombination value) - invalidEmailAndPasswordCombination, - }) { - return emailAlreadyInUse(this); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is ServerError); } @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(ServerError value)? serverError, - TResult? Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult? Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - }) { - return emailAlreadyInUse?.call(this); - } + int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(ServerError value)? serverError, - TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - required TResult orElse(), - }) { - if (emailAlreadyInUse != null) { - return emailAlreadyInUse(this); - } - return orElse(); + String toString() { + return 'AuthFailures.serverError()'; } } -abstract class EmailAlreadyInUse implements AuthFailures { - const factory EmailAlreadyInUse() = _$EmailAlreadyInUseImpl; -} - /// @nodoc -abstract class _$$InavalidEmailAndPasswordCombinationImplCopyWith<$Res> { - factory _$$InavalidEmailAndPasswordCombinationImplCopyWith( - _$InavalidEmailAndPasswordCombinationImpl value, - $Res Function(_$InavalidEmailAndPasswordCombinationImpl) then) = - __$$InavalidEmailAndPasswordCombinationImplCopyWithImpl<$Res>; -} -/// @nodoc -class __$$InavalidEmailAndPasswordCombinationImplCopyWithImpl<$Res> - extends _$AuthFailuresCopyWithImpl<$Res, - _$InavalidEmailAndPasswordCombinationImpl> - implements _$$InavalidEmailAndPasswordCombinationImplCopyWith<$Res> { - __$$InavalidEmailAndPasswordCombinationImplCopyWithImpl( - _$InavalidEmailAndPasswordCombinationImpl _value, - $Res Function(_$InavalidEmailAndPasswordCombinationImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$InavalidEmailAndPasswordCombinationImpl - implements InavalidEmailAndPasswordCombination { - const _$InavalidEmailAndPasswordCombinationImpl(); - - @override - String toString() { - return 'AuthFailures.invalidEmailAndPasswordCombination()'; - } +class EmailAlreadyInUse implements AuthFailures { + const EmailAlreadyInUse(); @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$InavalidEmailAndPasswordCombinationImpl); + (other.runtimeType == runtimeType && other is EmailAlreadyInUse); } @override int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function() serverError, - required TResult Function() emailAlreadyInUse, - required TResult Function() invalidEmailAndPasswordCombination, - }) { - return invalidEmailAndPasswordCombination(); + String toString() { + return 'AuthFailures.emailAlreadyInUse()'; } +} - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function()? serverError, - TResult? Function()? emailAlreadyInUse, - TResult? Function()? invalidEmailAndPasswordCombination, - }) { - return invalidEmailAndPasswordCombination?.call(); - } +/// @nodoc - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function()? serverError, - TResult Function()? emailAlreadyInUse, - TResult Function()? invalidEmailAndPasswordCombination, - required TResult orElse(), - }) { - if (invalidEmailAndPasswordCombination != null) { - return invalidEmailAndPasswordCombination(); - } - return orElse(); - } +class InavalidEmailAndPasswordCombination implements AuthFailures { + const InavalidEmailAndPasswordCombination(); @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(ServerError value) serverError, - required TResult Function(EmailAlreadyInUse value) emailAlreadyInUse, - required TResult Function(InavalidEmailAndPasswordCombination value) - invalidEmailAndPasswordCombination, - }) { - return invalidEmailAndPasswordCombination(this); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is InavalidEmailAndPasswordCombination); } @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(ServerError value)? serverError, - TResult? Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult? Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - }) { - return invalidEmailAndPasswordCombination?.call(this); - } + int get hashCode => runtimeType.hashCode; @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(ServerError value)? serverError, - TResult Function(EmailAlreadyInUse value)? emailAlreadyInUse, - TResult Function(InavalidEmailAndPasswordCombination value)? - invalidEmailAndPasswordCombination, - required TResult orElse(), - }) { - if (invalidEmailAndPasswordCombination != null) { - return invalidEmailAndPasswordCombination(this); - } - return orElse(); + String toString() { + return 'AuthFailures.invalidEmailAndPasswordCombination()'; } } -abstract class InavalidEmailAndPasswordCombination implements AuthFailures { - const factory InavalidEmailAndPasswordCombination() = - _$InavalidEmailAndPasswordCombinationImpl; -} +// dart format on diff --git a/lib/domain/authentication/auth_value_failures.dart b/lib/domain/authentication/auth_value_failures.dart index 66ba58d..6f93036 100644 --- a/lib/domain/authentication/auth_value_failures.dart +++ b/lib/domain/authentication/auth_value_failures.dart @@ -4,7 +4,7 @@ import "package:freezed_annotation/freezed_annotation.dart"; part "auth_value_failures.freezed.dart"; @freezed -class AuthValueFailures<T> with _$AuthValueFailures<T> { +sealed class AuthValueFailures<T> with _$AuthValueFailures<T> { const factory AuthValueFailures.invalidEmail({required String? failedValue}) = InvalidEmail<T>; diff --git a/lib/domain/authentication/auth_value_failures.freezed.dart b/lib/domain/authentication/auth_value_failures.freezed.dart index 27dadc2..9209fcb 100644 --- a/lib/domain/authentication/auth_value_failures.freezed.dart +++ b/lib/domain/authentication/auth_value_failures.freezed.dart @@ -1,5 +1,5 @@ -// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file // ignore_for_file: type=lint // 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 @@ -9,161 +9,26 @@ part of 'auth_value_failures.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity<T>(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$AuthValueFailures<T> { - String? get failedValue => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? failedValue)? invalidEmail, - TResult Function(String? failedValue)? shortPassword, - TResult Function(String? failedValue)? noSpecialSymbol, - TResult Function(String? failedValue)? noUpperCase, - TResult Function(String? failedValue)? noNumber, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(InvalidEmail<T> value) invalidEmail, - required TResult Function(ShortPassword<T> value) shortPassword, - required TResult Function(NoSpecialSymbol<T> value) noSpecialSymbol, - required TResult Function(NoUpperCase<T> value) noUpperCase, - required TResult Function(NoNumber<T> value) noNumber, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(InvalidEmail<T> value)? invalidEmail, - TResult? Function(ShortPassword<T> value)? shortPassword, - TResult? Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult? Function(NoUpperCase<T> value)? noUpperCase, - TResult? Function(NoNumber<T> value)? noNumber, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $AuthValueFailuresCopyWith<T, AuthValueFailures<T>> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $AuthValueFailuresCopyWith<T, $Res> { - factory $AuthValueFailuresCopyWith(AuthValueFailures<T> value, - $Res Function(AuthValueFailures<T>) then) = - _$AuthValueFailuresCopyWithImpl<T, $Res, AuthValueFailures<T>>; - @useResult - $Res call({String? failedValue}); -} - -/// @nodoc -class _$AuthValueFailuresCopyWithImpl<T, $Res, - $Val extends AuthValueFailures<T>> - implements $AuthValueFailuresCopyWith<T, $Res> { - _$AuthValueFailuresCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? failedValue = freezed, - }) { - return _then(_value.copyWith( - failedValue: freezed == failedValue - ? _value.failedValue - : failedValue // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$InvalidEmailImplCopyWith<T, $Res> - implements $AuthValueFailuresCopyWith<T, $Res> { - factory _$$InvalidEmailImplCopyWith(_$InvalidEmailImpl<T> value, - $Res Function(_$InvalidEmailImpl<T>) then) = - __$$InvalidEmailImplCopyWithImpl<T, $Res>; - @override - @useResult - $Res call({String? failedValue}); -} - -/// @nodoc -class __$$InvalidEmailImplCopyWithImpl<T, $Res> - extends _$AuthValueFailuresCopyWithImpl<T, $Res, _$InvalidEmailImpl<T>> - implements _$$InvalidEmailImplCopyWith<T, $Res> { - __$$InvalidEmailImplCopyWithImpl( - _$InvalidEmailImpl<T> _value, $Res Function(_$InvalidEmailImpl<T>) _then) - : super(_value, _then); + String? get failedValue; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') - @override - $Res call({ - Object? failedValue = freezed, - }) { - return _then(_$InvalidEmailImpl<T>( - failedValue: freezed == failedValue - ? _value.failedValue - : failedValue // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$InvalidEmailImpl<T> implements InvalidEmail<T> { - const _$InvalidEmailImpl({required this.failedValue}); - - @override - final String? failedValue; - - @override - String toString() { - return 'AuthValueFailures<$T>.invalidEmail(failedValue: $failedValue)'; - } + $AuthValueFailuresCopyWith<T, AuthValueFailures<T>> get copyWith => + _$AuthValueFailuresCopyWithImpl<T, AuthValueFailures<T>>( + this as AuthValueFailures<T>, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$InvalidEmailImpl<T> && + other is AuthValueFailures<T> && (identical(other.failedValue, failedValue) || other.failedValue == failedValue)); } @@ -171,212 +36,98 @@ class _$InvalidEmailImpl<T> implements InvalidEmail<T> { @override int get hashCode => Object.hash(runtimeType, failedValue); - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$InvalidEmailImplCopyWith<T, _$InvalidEmailImpl<T>> get copyWith => - __$$InvalidEmailImplCopyWithImpl<T, _$InvalidEmailImpl<T>>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) { - return invalidEmail(failedValue); - } - - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, - }) { - return invalidEmail?.call(failedValue); - } - - @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? failedValue)? invalidEmail, - TResult Function(String? failedValue)? shortPassword, - TResult Function(String? failedValue)? noSpecialSymbol, - TResult Function(String? failedValue)? noUpperCase, - TResult Function(String? failedValue)? noNumber, - required TResult orElse(), - }) { - if (invalidEmail != null) { - return invalidEmail(failedValue); - } - return orElse(); - } - @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(InvalidEmail<T> value) invalidEmail, - required TResult Function(ShortPassword<T> value) shortPassword, - required TResult Function(NoSpecialSymbol<T> value) noSpecialSymbol, - required TResult Function(NoUpperCase<T> value) noUpperCase, - required TResult Function(NoNumber<T> value) noNumber, - }) { - return invalidEmail(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(InvalidEmail<T> value)? invalidEmail, - TResult? Function(ShortPassword<T> value)? shortPassword, - TResult? Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult? Function(NoUpperCase<T> value)? noUpperCase, - TResult? Function(NoNumber<T> value)? noNumber, - }) { - return invalidEmail?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), - }) { - if (invalidEmail != null) { - return invalidEmail(this); - } - return orElse(); + String toString() { + return 'AuthValueFailures<$T>(failedValue: $failedValue)'; } } -abstract class InvalidEmail<T> implements AuthValueFailures<T> { - const factory InvalidEmail({required final String? failedValue}) = - _$InvalidEmailImpl<T>; - - @override - String? get failedValue; - @override - @JsonKey(ignore: true) - _$$InvalidEmailImplCopyWith<T, _$InvalidEmailImpl<T>> get copyWith => - throw _privateConstructorUsedError; -} - /// @nodoc -abstract class _$$ShortPasswordImplCopyWith<T, $Res> - implements $AuthValueFailuresCopyWith<T, $Res> { - factory _$$ShortPasswordImplCopyWith(_$ShortPasswordImpl<T> value, - $Res Function(_$ShortPasswordImpl<T>) then) = - __$$ShortPasswordImplCopyWithImpl<T, $Res>; - @override +abstract mixin class $AuthValueFailuresCopyWith<T, $Res> { + factory $AuthValueFailuresCopyWith(AuthValueFailures<T> value, + $Res Function(AuthValueFailures<T>) _then) = + _$AuthValueFailuresCopyWithImpl; @useResult $Res call({String? failedValue}); } /// @nodoc -class __$$ShortPasswordImplCopyWithImpl<T, $Res> - extends _$AuthValueFailuresCopyWithImpl<T, $Res, _$ShortPasswordImpl<T>> - implements _$$ShortPasswordImplCopyWith<T, $Res> { - __$$ShortPasswordImplCopyWithImpl(_$ShortPasswordImpl<T> _value, - $Res Function(_$ShortPasswordImpl<T>) _then) - : super(_value, _then); +class _$AuthValueFailuresCopyWithImpl<T, $Res> + implements $AuthValueFailuresCopyWith<T, $Res> { + _$AuthValueFailuresCopyWithImpl(this._self, this._then); + + final AuthValueFailures<T> _self; + final $Res Function(AuthValueFailures<T>) _then; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? failedValue = freezed, }) { - return _then(_$ShortPasswordImpl<T>( + return _then(_self.copyWith( failedValue: freezed == failedValue - ? _value.failedValue + ? _self.failedValue : failedValue // ignore: cast_nullable_to_non_nullable as String?, )); } } -/// @nodoc - -class _$ShortPasswordImpl<T> implements ShortPassword<T> { - const _$ShortPasswordImpl({required this.failedValue}); - - @override - final String? failedValue; - - @override - String toString() { - return 'AuthValueFailures<$T>.shortPassword(failedValue: $failedValue)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$ShortPasswordImpl<T> && - (identical(other.failedValue, failedValue) || - other.failedValue == failedValue)); - } - - @override - int get hashCode => Object.hash(runtimeType, failedValue); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$ShortPasswordImplCopyWith<T, _$ShortPasswordImpl<T>> get copyWith => - __$$ShortPasswordImplCopyWithImpl<T, _$ShortPasswordImpl<T>>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) { - return shortPassword(failedValue); - } - - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, - }) { - return shortPassword?.call(failedValue); - } +/// Adds pattern-matching-related methods to [AuthValueFailures]. +extension AuthValueFailuresPatterns<T> on AuthValueFailures<T> { + /// A variant of `map` that fallback to returning `orElse`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` - @override @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? failedValue)? invalidEmail, - TResult Function(String? failedValue)? shortPassword, - TResult Function(String? failedValue)? noSpecialSymbol, - TResult Function(String? failedValue)? noUpperCase, - TResult Function(String? failedValue)? noNumber, + TResult maybeMap<TResult extends Object?>({ + TResult Function(InvalidEmail<T> value)? invalidEmail, + TResult Function(ShortPassword<T> value)? shortPassword, + TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, + TResult Function(NoUpperCase<T> value)? noUpperCase, + TResult Function(NoNumber<T> value)? noNumber, required TResult orElse(), }) { - if (shortPassword != null) { - return shortPassword(failedValue); + final _that = this; + switch (_that) { + case InvalidEmail() when invalidEmail != null: + return invalidEmail(_that); + case ShortPassword() when shortPassword != null: + return shortPassword(_that); + case NoSpecialSymbol() when noSpecialSymbol != null: + return noSpecialSymbol(_that); + case NoUpperCase() when noUpperCase != null: + return noUpperCase(_that); + case NoNumber() when noNumber != null: + return noNumber(_that); + case _: + return orElse(); } - return orElse(); } - @override + /// A `switch`-like method, using callbacks. + /// + /// Callbacks receives the raw object, upcasted. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case final Subclass2 value: + /// return ...; + /// } + /// ``` + @optionalTypeArgs TResult map<TResult extends Object?>({ required TResult Function(InvalidEmail<T> value) invalidEmail, @@ -385,10 +136,33 @@ class _$ShortPasswordImpl<T> implements ShortPassword<T> { required TResult Function(NoUpperCase<T> value) noUpperCase, required TResult Function(NoNumber<T> value) noNumber, }) { - return shortPassword(this); + final _that = this; + switch (_that) { + case InvalidEmail(): + return invalidEmail(_that); + case ShortPassword(): + return shortPassword(_that); + case NoSpecialSymbol(): + return noSpecialSymbol(_that); + case NoUpperCase(): + return noUpperCase(_that); + case NoNumber(): + return noNumber(_that); + } } - @override + /// A variant of `map` that fallback to returning `null`. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case final Subclass value: + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({ TResult? Function(InvalidEmail<T> value)? invalidEmail, @@ -397,128 +171,35 @@ class _$ShortPasswordImpl<T> implements ShortPassword<T> { TResult? Function(NoUpperCase<T> value)? noUpperCase, TResult? Function(NoNumber<T> value)? noNumber, }) { - return shortPassword?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), - }) { - if (shortPassword != null) { - return shortPassword(this); + final _that = this; + switch (_that) { + case InvalidEmail() when invalidEmail != null: + return invalidEmail(_that); + case ShortPassword() when shortPassword != null: + return shortPassword(_that); + case NoSpecialSymbol() when noSpecialSymbol != null: + return noSpecialSymbol(_that); + case NoUpperCase() when noUpperCase != null: + return noUpperCase(_that); + case NoNumber() when noNumber != null: + return noNumber(_that); + case _: + return null; } - return orElse(); - } -} - -abstract class ShortPassword<T> implements AuthValueFailures<T> { - const factory ShortPassword({required final String? failedValue}) = - _$ShortPasswordImpl<T>; - - @override - String? get failedValue; - @override - @JsonKey(ignore: true) - _$$ShortPasswordImplCopyWith<T, _$ShortPasswordImpl<T>> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$NoSpecialSymbolImplCopyWith<T, $Res> - implements $AuthValueFailuresCopyWith<T, $Res> { - factory _$$NoSpecialSymbolImplCopyWith(_$NoSpecialSymbolImpl<T> value, - $Res Function(_$NoSpecialSymbolImpl<T>) then) = - __$$NoSpecialSymbolImplCopyWithImpl<T, $Res>; - @override - @useResult - $Res call({String? failedValue}); -} - -/// @nodoc -class __$$NoSpecialSymbolImplCopyWithImpl<T, $Res> - extends _$AuthValueFailuresCopyWithImpl<T, $Res, _$NoSpecialSymbolImpl<T>> - implements _$$NoSpecialSymbolImplCopyWith<T, $Res> { - __$$NoSpecialSymbolImplCopyWithImpl(_$NoSpecialSymbolImpl<T> _value, - $Res Function(_$NoSpecialSymbolImpl<T>) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? failedValue = freezed, - }) { - return _then(_$NoSpecialSymbolImpl<T>( - failedValue: freezed == failedValue - ? _value.failedValue - : failedValue // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$NoSpecialSymbolImpl<T> implements NoSpecialSymbol<T> { - const _$NoSpecialSymbolImpl({required this.failedValue}); - - @override - final String? failedValue; - - @override - String toString() { - return 'AuthValueFailures<$T>.noSpecialSymbol(failedValue: $failedValue)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NoSpecialSymbolImpl<T> && - (identical(other.failedValue, failedValue) || - other.failedValue == failedValue)); - } - - @override - int get hashCode => Object.hash(runtimeType, failedValue); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$NoSpecialSymbolImplCopyWith<T, _$NoSpecialSymbolImpl<T>> get copyWith => - __$$NoSpecialSymbolImplCopyWithImpl<T, _$NoSpecialSymbolImpl<T>>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) { - return noSpecialSymbol(failedValue); } - @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, - }) { - return noSpecialSymbol?.call(failedValue); - } + /// A variant of `when` that fallback to an `orElse` callback. + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return orElse(); + /// } + /// ``` - @override @optionalTypeArgs TResult maybeWhen<TResult extends Object?>({ TResult Function(String? failedValue)? invalidEmail, @@ -528,92 +209,160 @@ class _$NoSpecialSymbolImpl<T> implements NoSpecialSymbol<T> { TResult Function(String? failedValue)? noNumber, required TResult orElse(), }) { - if (noSpecialSymbol != null) { - return noSpecialSymbol(failedValue); + final _that = this; + switch (_that) { + case InvalidEmail() when invalidEmail != null: + return invalidEmail(_that.failedValue); + case ShortPassword() when shortPassword != null: + return shortPassword(_that.failedValue); + case NoSpecialSymbol() when noSpecialSymbol != null: + return noSpecialSymbol(_that.failedValue); + case NoUpperCase() when noUpperCase != null: + return noUpperCase(_that.failedValue); + case NoNumber() when noNumber != null: + return noNumber(_that.failedValue); + case _: + return orElse(); } - return orElse(); } - @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(InvalidEmail<T> value) invalidEmail, - required TResult Function(ShortPassword<T> value) shortPassword, - required TResult Function(NoSpecialSymbol<T> value) noSpecialSymbol, - required TResult Function(NoUpperCase<T> value) noUpperCase, - required TResult Function(NoNumber<T> value) noNumber, - }) { - return noSpecialSymbol(this); - } + /// A `switch`-like method, using callbacks. + /// + /// As opposed to `map`, this offers destructuring. + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case Subclass2(:final field2): + /// return ...; + /// } + /// ``` - @override @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(InvalidEmail<T> value)? invalidEmail, - TResult? Function(ShortPassword<T> value)? shortPassword, - TResult? Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult? Function(NoUpperCase<T> value)? noUpperCase, - TResult? Function(NoNumber<T> value)? noNumber, + TResult when<TResult extends Object?>({ + required TResult Function(String? failedValue) invalidEmail, + required TResult Function(String? failedValue) shortPassword, + required TResult Function(String? failedValue) noSpecialSymbol, + required TResult Function(String? failedValue) noUpperCase, + required TResult Function(String? failedValue) noNumber, }) { - return noSpecialSymbol?.call(this); + final _that = this; + switch (_that) { + case InvalidEmail(): + return invalidEmail(_that.failedValue); + case ShortPassword(): + return shortPassword(_that.failedValue); + case NoSpecialSymbol(): + return noSpecialSymbol(_that.failedValue); + case NoUpperCase(): + return noUpperCase(_that.failedValue); + case NoNumber(): + return noNumber(_that.failedValue); + } } - @override + /// A variant of `when` that fallback to returning `null` + /// + /// It is equivalent to doing: + /// ```dart + /// switch (sealedClass) { + /// case Subclass(:final field): + /// return ...; + /// case _: + /// return null; + /// } + /// ``` + @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), + TResult? whenOrNull<TResult extends Object?>({ + TResult? Function(String? failedValue)? invalidEmail, + TResult? Function(String? failedValue)? shortPassword, + TResult? Function(String? failedValue)? noSpecialSymbol, + TResult? Function(String? failedValue)? noUpperCase, + TResult? Function(String? failedValue)? noNumber, }) { - if (noSpecialSymbol != null) { - return noSpecialSymbol(this); + final _that = this; + switch (_that) { + case InvalidEmail() when invalidEmail != null: + return invalidEmail(_that.failedValue); + case ShortPassword() when shortPassword != null: + return shortPassword(_that.failedValue); + case NoSpecialSymbol() when noSpecialSymbol != null: + return noSpecialSymbol(_that.failedValue); + case NoUpperCase() when noUpperCase != null: + return noUpperCase(_that.failedValue); + case NoNumber() when noNumber != null: + return noNumber(_that.failedValue); + case _: + return null; } - return orElse(); } } -abstract class NoSpecialSymbol<T> implements AuthValueFailures<T> { - const factory NoSpecialSymbol({required final String? failedValue}) = - _$NoSpecialSymbolImpl<T>; +/// @nodoc + +class InvalidEmail<T> implements AuthValueFailures<T> { + const InvalidEmail({required this.failedValue}); @override - String? get failedValue; + final String? failedValue; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $InvalidEmailCopyWith<T, InvalidEmail<T>> get copyWith => + _$InvalidEmailCopyWithImpl<T, InvalidEmail<T>>(this, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is InvalidEmail<T> && + (identical(other.failedValue, failedValue) || + other.failedValue == failedValue)); + } + + @override + int get hashCode => Object.hash(runtimeType, failedValue); + @override - @JsonKey(ignore: true) - _$$NoSpecialSymbolImplCopyWith<T, _$NoSpecialSymbolImpl<T>> get copyWith => - throw _privateConstructorUsedError; + String toString() { + return 'AuthValueFailures<$T>.invalidEmail(failedValue: $failedValue)'; + } } /// @nodoc -abstract class _$$NoUpperCaseImplCopyWith<T, $Res> +abstract mixin class $InvalidEmailCopyWith<T, $Res> implements $AuthValueFailuresCopyWith<T, $Res> { - factory _$$NoUpperCaseImplCopyWith(_$NoUpperCaseImpl<T> value, - $Res Function(_$NoUpperCaseImpl<T>) then) = - __$$NoUpperCaseImplCopyWithImpl<T, $Res>; + factory $InvalidEmailCopyWith( + InvalidEmail<T> value, $Res Function(InvalidEmail<T>) _then) = + _$InvalidEmailCopyWithImpl; @override @useResult $Res call({String? failedValue}); } /// @nodoc -class __$$NoUpperCaseImplCopyWithImpl<T, $Res> - extends _$AuthValueFailuresCopyWithImpl<T, $Res, _$NoUpperCaseImpl<T>> - implements _$$NoUpperCaseImplCopyWith<T, $Res> { - __$$NoUpperCaseImplCopyWithImpl( - _$NoUpperCaseImpl<T> _value, $Res Function(_$NoUpperCaseImpl<T>) _then) - : super(_value, _then); +class _$InvalidEmailCopyWithImpl<T, $Res> + implements $InvalidEmailCopyWith<T, $Res> { + _$InvalidEmailCopyWithImpl(this._self, this._then); - @pragma('vm:prefer-inline') + final InvalidEmail<T> _self; + final $Res Function(InvalidEmail<T>) _then; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override + @pragma('vm:prefer-inline') $Res call({ Object? failedValue = freezed, }) { - return _then(_$NoUpperCaseImpl<T>( + return _then(InvalidEmail<T>( failedValue: freezed == failedValue - ? _value.failedValue + ? _self.failedValue : failedValue // ignore: cast_nullable_to_non_nullable as String?, )); @@ -622,22 +371,25 @@ class __$$NoUpperCaseImplCopyWithImpl<T, $Res> /// @nodoc -class _$NoUpperCaseImpl<T> implements NoUpperCase<T> { - const _$NoUpperCaseImpl({required this.failedValue}); +class ShortPassword<T> implements AuthValueFailures<T> { + const ShortPassword({required this.failedValue}); @override final String? failedValue; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'AuthValueFailures<$T>.noUpperCase(failedValue: $failedValue)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $ShortPasswordCopyWith<T, ShortPassword<T>> get copyWith => + _$ShortPasswordCopyWithImpl<T, ShortPassword<T>>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NoUpperCaseImpl<T> && + other is ShortPassword<T> && (identical(other.failedValue, failedValue) || other.failedValue == failedValue)); } @@ -645,133 +397,110 @@ class _$NoUpperCaseImpl<T> implements NoUpperCase<T> { @override int get hashCode => Object.hash(runtimeType, failedValue); - @JsonKey(ignore: true) @override - @pragma('vm:prefer-inline') - _$$NoUpperCaseImplCopyWith<T, _$NoUpperCaseImpl<T>> get copyWith => - __$$NoUpperCaseImplCopyWithImpl<T, _$NoUpperCaseImpl<T>>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) { - return noUpperCase(failedValue); + String toString() { + return 'AuthValueFailures<$T>.shortPassword(failedValue: $failedValue)'; } +} +/// @nodoc +abstract mixin class $ShortPasswordCopyWith<T, $Res> + implements $AuthValueFailuresCopyWith<T, $Res> { + factory $ShortPasswordCopyWith( + ShortPassword<T> value, $Res Function(ShortPassword<T>) _then) = + _$ShortPasswordCopyWithImpl; @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, - }) { - return noUpperCase?.call(failedValue); - } + @useResult + $Res call({String? failedValue}); +} +/// @nodoc +class _$ShortPasswordCopyWithImpl<T, $Res> + implements $ShortPasswordCopyWith<T, $Res> { + _$ShortPasswordCopyWithImpl(this._self, this._then); + + final ShortPassword<T> _self; + final $Res Function(ShortPassword<T>) _then; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? failedValue)? invalidEmail, - TResult Function(String? failedValue)? shortPassword, - TResult Function(String? failedValue)? noSpecialSymbol, - TResult Function(String? failedValue)? noUpperCase, - TResult Function(String? failedValue)? noNumber, - required TResult orElse(), + @pragma('vm:prefer-inline') + $Res call({ + Object? failedValue = freezed, }) { - if (noUpperCase != null) { - return noUpperCase(failedValue); - } - return orElse(); + return _then(ShortPassword<T>( + failedValue: freezed == failedValue + ? _self.failedValue + : failedValue // ignore: cast_nullable_to_non_nullable + as String?, + )); } +} + +/// @nodoc + +class NoSpecialSymbol<T> implements AuthValueFailures<T> { + const NoSpecialSymbol({required this.failedValue}); @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(InvalidEmail<T> value) invalidEmail, - required TResult Function(ShortPassword<T> value) shortPassword, - required TResult Function(NoSpecialSymbol<T> value) noSpecialSymbol, - required TResult Function(NoUpperCase<T> value) noUpperCase, - required TResult Function(NoNumber<T> value) noNumber, - }) { - return noUpperCase(this); - } + final String? failedValue; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(InvalidEmail<T> value)? invalidEmail, - TResult? Function(ShortPassword<T> value)? shortPassword, - TResult? Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult? Function(NoUpperCase<T> value)? noUpperCase, - TResult? Function(NoNumber<T> value)? noNumber, - }) { - return noUpperCase?.call(this); - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $NoSpecialSymbolCopyWith<T, NoSpecialSymbol<T>> get copyWith => + _$NoSpecialSymbolCopyWithImpl<T, NoSpecialSymbol<T>>(this, _$identity); @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), - }) { - if (noUpperCase != null) { - return noUpperCase(this); - } - return orElse(); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is NoSpecialSymbol<T> && + (identical(other.failedValue, failedValue) || + other.failedValue == failedValue)); } -} - -abstract class NoUpperCase<T> implements AuthValueFailures<T> { - const factory NoUpperCase({required final String? failedValue}) = - _$NoUpperCaseImpl<T>; @override - String? get failedValue; + int get hashCode => Object.hash(runtimeType, failedValue); + @override - @JsonKey(ignore: true) - _$$NoUpperCaseImplCopyWith<T, _$NoUpperCaseImpl<T>> get copyWith => - throw _privateConstructorUsedError; + String toString() { + return 'AuthValueFailures<$T>.noSpecialSymbol(failedValue: $failedValue)'; + } } /// @nodoc -abstract class _$$NoNumberImplCopyWith<T, $Res> +abstract mixin class $NoSpecialSymbolCopyWith<T, $Res> implements $AuthValueFailuresCopyWith<T, $Res> { - factory _$$NoNumberImplCopyWith( - _$NoNumberImpl<T> value, $Res Function(_$NoNumberImpl<T>) then) = - __$$NoNumberImplCopyWithImpl<T, $Res>; + factory $NoSpecialSymbolCopyWith( + NoSpecialSymbol<T> value, $Res Function(NoSpecialSymbol<T>) _then) = + _$NoSpecialSymbolCopyWithImpl; @override @useResult $Res call({String? failedValue}); } /// @nodoc -class __$$NoNumberImplCopyWithImpl<T, $Res> - extends _$AuthValueFailuresCopyWithImpl<T, $Res, _$NoNumberImpl<T>> - implements _$$NoNumberImplCopyWith<T, $Res> { - __$$NoNumberImplCopyWithImpl( - _$NoNumberImpl<T> _value, $Res Function(_$NoNumberImpl<T>) _then) - : super(_value, _then); +class _$NoSpecialSymbolCopyWithImpl<T, $Res> + implements $NoSpecialSymbolCopyWith<T, $Res> { + _$NoSpecialSymbolCopyWithImpl(this._self, this._then); - @pragma('vm:prefer-inline') + final NoSpecialSymbol<T> _self; + final $Res Function(NoSpecialSymbol<T>) _then; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override + @pragma('vm:prefer-inline') $Res call({ Object? failedValue = freezed, }) { - return _then(_$NoNumberImpl<T>( + return _then(NoSpecialSymbol<T>( failedValue: freezed == failedValue - ? _value.failedValue + ? _self.failedValue : failedValue // ignore: cast_nullable_to_non_nullable as String?, )); @@ -780,22 +509,25 @@ class __$$NoNumberImplCopyWithImpl<T, $Res> /// @nodoc -class _$NoNumberImpl<T> implements NoNumber<T> { - const _$NoNumberImpl({required this.failedValue}); +class NoUpperCase<T> implements AuthValueFailures<T> { + const NoUpperCase({required this.failedValue}); @override final String? failedValue; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'AuthValueFailures<$T>.noNumber(failedValue: $failedValue)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $NoUpperCaseCopyWith<T, NoUpperCase<T>> get copyWith => + _$NoUpperCaseCopyWithImpl<T, NoUpperCase<T>>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NoNumberImpl<T> && + other is NoUpperCase<T> && (identical(other.failedValue, failedValue) || other.failedValue == failedValue)); } @@ -803,101 +535,113 @@ class _$NoNumberImpl<T> implements NoNumber<T> { @override int get hashCode => Object.hash(runtimeType, failedValue); - @JsonKey(ignore: true) @override - @pragma('vm:prefer-inline') - _$$NoNumberImplCopyWith<T, _$NoNumberImpl<T>> get copyWith => - __$$NoNumberImplCopyWithImpl<T, _$NoNumberImpl<T>>(this, _$identity); + String toString() { + return 'AuthValueFailures<$T>.noUpperCase(failedValue: $failedValue)'; + } +} +/// @nodoc +abstract mixin class $NoUpperCaseCopyWith<T, $Res> + implements $AuthValueFailuresCopyWith<T, $Res> { + factory $NoUpperCaseCopyWith( + NoUpperCase<T> value, $Res Function(NoUpperCase<T>) _then) = + _$NoUpperCaseCopyWithImpl; @override - @optionalTypeArgs - TResult when<TResult extends Object?>({ - required TResult Function(String? failedValue) invalidEmail, - required TResult Function(String? failedValue) shortPassword, - required TResult Function(String? failedValue) noSpecialSymbol, - required TResult Function(String? failedValue) noUpperCase, - required TResult Function(String? failedValue) noNumber, - }) { - return noNumber(failedValue); - } + @useResult + $Res call({String? failedValue}); +} +/// @nodoc +class _$NoUpperCaseCopyWithImpl<T, $Res> + implements $NoUpperCaseCopyWith<T, $Res> { + _$NoUpperCaseCopyWithImpl(this._self, this._then); + + final NoUpperCase<T> _self; + final $Res Function(NoUpperCase<T>) _then; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - @optionalTypeArgs - TResult? whenOrNull<TResult extends Object?>({ - TResult? Function(String? failedValue)? invalidEmail, - TResult? Function(String? failedValue)? shortPassword, - TResult? Function(String? failedValue)? noSpecialSymbol, - TResult? Function(String? failedValue)? noUpperCase, - TResult? Function(String? failedValue)? noNumber, + @pragma('vm:prefer-inline') + $Res call({ + Object? failedValue = freezed, }) { - return noNumber?.call(failedValue); + return _then(NoUpperCase<T>( + failedValue: freezed == failedValue + ? _self.failedValue + : failedValue // ignore: cast_nullable_to_non_nullable + as String?, + )); } +} + +/// @nodoc + +class NoNumber<T> implements AuthValueFailures<T> { + const NoNumber({required this.failedValue}); @override - @optionalTypeArgs - TResult maybeWhen<TResult extends Object?>({ - TResult Function(String? failedValue)? invalidEmail, - TResult Function(String? failedValue)? shortPassword, - TResult Function(String? failedValue)? noSpecialSymbol, - TResult Function(String? failedValue)? noUpperCase, - TResult Function(String? failedValue)? noNumber, - required TResult orElse(), - }) { - if (noNumber != null) { - return noNumber(failedValue); - } - return orElse(); - } + final String? failedValue; + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - @optionalTypeArgs - TResult map<TResult extends Object?>({ - required TResult Function(InvalidEmail<T> value) invalidEmail, - required TResult Function(ShortPassword<T> value) shortPassword, - required TResult Function(NoSpecialSymbol<T> value) noSpecialSymbol, - required TResult Function(NoUpperCase<T> value) noUpperCase, - required TResult Function(NoNumber<T> value) noNumber, - }) { - return noNumber(this); - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $NoNumberCopyWith<T, NoNumber<T>> get copyWith => + _$NoNumberCopyWithImpl<T, NoNumber<T>>(this, _$identity); @override - @optionalTypeArgs - TResult? mapOrNull<TResult extends Object?>({ - TResult? Function(InvalidEmail<T> value)? invalidEmail, - TResult? Function(ShortPassword<T> value)? shortPassword, - TResult? Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult? Function(NoUpperCase<T> value)? noUpperCase, - TResult? Function(NoNumber<T> value)? noNumber, - }) { - return noNumber?.call(this); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is NoNumber<T> && + (identical(other.failedValue, failedValue) || + other.failedValue == failedValue)); } @override - @optionalTypeArgs - TResult maybeMap<TResult extends Object?>({ - TResult Function(InvalidEmail<T> value)? invalidEmail, - TResult Function(ShortPassword<T> value)? shortPassword, - TResult Function(NoSpecialSymbol<T> value)? noSpecialSymbol, - TResult Function(NoUpperCase<T> value)? noUpperCase, - TResult Function(NoNumber<T> value)? noNumber, - required TResult orElse(), - }) { - if (noNumber != null) { - return noNumber(this); - } - return orElse(); + int get hashCode => Object.hash(runtimeType, failedValue); + + @override + String toString() { + return 'AuthValueFailures<$T>.noNumber(failedValue: $failedValue)'; } } -abstract class NoNumber<T> implements AuthValueFailures<T> { - const factory NoNumber({required final String? failedValue}) = - _$NoNumberImpl<T>; - +/// @nodoc +abstract mixin class $NoNumberCopyWith<T, $Res> + implements $AuthValueFailuresCopyWith<T, $Res> { + factory $NoNumberCopyWith( + NoNumber<T> value, $Res Function(NoNumber<T>) _then) = + _$NoNumberCopyWithImpl; @override - String? get failedValue; + @useResult + $Res call({String? failedValue}); +} + +/// @nodoc +class _$NoNumberCopyWithImpl<T, $Res> implements $NoNumberCopyWith<T, $Res> { + _$NoNumberCopyWithImpl(this._self, this._then); + + final NoNumber<T> _self; + final $Res Function(NoNumber<T>) _then; + + /// Create a copy of AuthValueFailures + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) - _$$NoNumberImplCopyWith<T, _$NoNumberImpl<T>> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? failedValue = freezed, + }) { + return _then(NoNumber<T>( + failedValue: freezed == failedValue + ? _self.failedValue + : failedValue // ignore: cast_nullable_to_non_nullable + as String?, + )); + } } + +// dart format on diff --git a/lib/domain/authentication/auth_value_objects.dart b/lib/domain/authentication/auth_value_objects.dart index dc949d7..3bf2cd6 100644 --- a/lib/domain/authentication/auth_value_objects.dart +++ b/lib/domain/authentication/auth_value_objects.dart @@ -1,6 +1,6 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_failures.dart"; -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_validators.dart"; -import "package:firebase_auth_flutter_ddd/Domain/Core/value_object.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_validators.dart"; +import "package:firebase_auth_flutter_ddd/domain/core/value_object.dart"; import "package:fpdart/fpdart.dart"; class EmailAddress extends ValueObject<String> { diff --git a/lib/domain/authentication/auth_value_validators.dart b/lib/domain/authentication/auth_value_validators.dart index f6a146f..0d77fc6 100644 --- a/lib/domain/authentication/auth_value_validators.dart +++ b/lib/domain/authentication/auth_value_validators.dart @@ -1,11 +1,10 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart"; import "package:fpdart/fpdart.dart"; Either<AuthValueFailures<String>, String> validateEmailAddress({ required String? email, }) { - final emailRegex = RegExp( - r'^[a-zA-Z0-9.a-zA-Z0-9.!#$%&"*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+'); + final emailRegex = RegExp(r'^[a-zA-Z0-9.a-zA-Z0-9.!#$%&"*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+'); if (emailRegex.hasMatch(email!)) { return right(email); @@ -22,8 +21,7 @@ Either<AuthValueFailures<String>, String> validatePassword({ final hasMinLength = password!.length > 6; final hasUppercase = password.contains(RegExp("[A-Z]")); final hasDigits = password.contains(RegExp("[0-9]")); - final hasSpecialCharacters = - password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]')); + final hasSpecialCharacters = password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]')); if (!hasMinLength) { return left( AuthValueFailures.shortPassword(failedValue: password), diff --git a/lib/domain/authentication/i_auth_facade.dart b/lib/domain/authentication/i_auth_facade.dart index 92d924e..f856d0e 100644 --- a/lib/domain/authentication/i_auth_facade.dart +++ b/lib/domain/authentication/i_auth_facade.dart @@ -1,5 +1,5 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_failures.dart"; -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_objects.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_objects.dart"; import "package:fpdart/fpdart.dart"; diff --git a/lib/domain/core/errors.dart b/lib/domain/core/errors.dart index 2bd2d3f..7c50470 100644 --- a/lib/domain/core/errors.dart +++ b/lib/domain/core/errors.dart @@ -1,4 +1,4 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart"; class UnExpectedValueError extends Error { UnExpectedValueError(this.authValueFailures); diff --git a/lib/domain/core/value_object.dart b/lib/domain/core/value_object.dart index 327bd19..f470e74 100644 --- a/lib/domain/core/value_object.dart +++ b/lib/domain/core/value_object.dart @@ -1,4 +1,4 @@ -import "package:firebase_auth_flutter_ddd/Domain/Authentication/auth_value_failures.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_value_failures.dart"; import "package:flutter/cupertino.dart"; import "package:fpdart/fpdart.dart"; diff --git a/lib/screens/login_page.dart b/lib/screens/login_page.dart index 93b2561..8207d18 100644 --- a/lib/screens/login_page.dart +++ b/lib/screens/login_page.dart @@ -1,21 +1,12 @@ -import "package:firebase_auth/firebase_auth.dart"; -import "package:firebase_auth_flutter_ddd/Screens/home_page.dart"; +import "package:firebase_auth_flutter_ddd/domain/authentication/auth_failures.dart"; +import "package:firebase_auth_flutter_ddd/screens/home_page.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "../Application/Authentication/auth_events.dart"; -import "../Application/Authentication/auth_state_controller.dart"; -import "../Application/Authentication/auth_states.dart"; -import "../Services/Authentication/firebase_auth_facade.dart"; -import "Utils/custom_snackbar.dart"; - -final loginProvider = - StateNotifierProvider.autoDispose<AuthStateController, AuthStates>((ref) { - final firebaseAuth = FirebaseAuth.instance; - final firebaseAuthFacade = FirebaseAuthFacade(firebaseAuth); - return AuthStateController(firebaseAuthFacade); -}); +import "../application/authentication/auth_state_controller.dart"; +import "../application/authentication/auth_states.dart"; +import "utils/custom_snackbar.dart"; class LoginPage extends HookConsumerWidget { LoginPage({Key? key}) : super(key: key); @@ -24,9 +15,10 @@ class LoginPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final formStates = ref.watch(loginProvider); - final formEvents = ref.watch(loginProvider.notifier); - ref.listen<AuthStates>(loginProvider, (previous, next) { + final formStates = ref.watch(authStateControllerProvider); + final formNotifier = ref.watch(authStateControllerProvider.notifier); + + ref.listen<AuthStates>(authStateControllerProvider, (previous, next) { next.authFailureOrSuccess.fold( () {}, (either) => either.fold( @@ -45,10 +37,7 @@ class LoginPage extends HookConsumerWidget { invalidEmailAndPasswordCombination: (value) { return "Invalid email or password"; }), - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith(color: Colors.white), + style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Colors.white), )); }, (success) { @@ -58,10 +47,7 @@ class LoginPage extends HookConsumerWidget { icon: CupertinoIcons.check_mark_circled_solid, content: Text( "Login successful", - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith(color: Colors.white), + style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Colors.white), )); Navigator.push<Widget>( context, @@ -99,135 +85,63 @@ class LoginPage extends HookConsumerWidget { reverse: true, padding: const EdgeInsets.all(20), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - "Welcome !", - style: Theme.of(context).textTheme.displayMedium, - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Text( - "Login or Signup to continue", - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 20), TextFormField( - autovalidateMode: AutovalidateMode.onUserInteraction, - onChanged: (value) => formEvents.mapEventsToStates( - AuthEvents.emailChanged(email: value.toString())), - validator: (value) => - formStates.emailAddress.valueObject!.fold( - (failure) => failure.maybeMap( - orElse: () => null, - invalidEmail: (value) => "Invalid email"), - (r) => null, - ), - textInputAction: TextInputAction.next, - onEditingComplete: () => - FocusScope.of(context).nextFocus(), decoration: const InputDecoration( - labelText: "Email Address", + labelText: "Email", border: OutlineInputBorder(), ), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter an email"; + } + return null; + }, + onChanged: (value) { + formNotifier.emailChanged(value); + }, ), - const SizedBox( - height: 15, - ), + const SizedBox(height: 20), TextFormField( - autovalidateMode: AutovalidateMode.onUserInteraction, - textInputAction: TextInputAction.done, - onEditingComplete: () => - FocusScope.of(context).nextFocus(), - validator: (value) => - formStates.password.valueObject!.fold( - (failure) => failure.maybeMap( - orElse: () => null, - shortPassword: (value) => "Very short password", - noUpperCase: (value) => - "Must contain an uppercase character", - noNumber: (value) => "Must contain a number", - noSpecialSymbol: (value) => - "Must contain a special character", - ), - (r) => null, - ), - onChanged: (value) => formEvents.mapEventsToStates( - AuthEvents.passwordChanged( - password: value.toString()), - ), obscureText: true, decoration: const InputDecoration( labelText: "Password", border: OutlineInputBorder(), ), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter a password"; + } + return null; + }, + onChanged: (value) { + formNotifier.passwordChanged(value); + }, ), - const SizedBox( - height: 20, + const SizedBox(height: 30), + ElevatedButton( + onPressed: formStates.isSubmitting + ? null + : () async { + if (formKey.currentState!.validate()) { + await formNotifier.signInWithEmailAndPassword(); + } + }, + child: formStates.isSubmitting ? const CircularProgressIndicator() : const Text("Sign In"), ), - Row( - children: [ - Expanded( - child: SizedBox( - height: 55, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: - Theme.of(context).textTheme.titleLarge, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12))), - onPressed: () { - if (formKey.currentState!.validate()) { - formEvents.mapEventsToStates( - const AuthEvents - .signInWithEmailAndPasswordPressed(), - ); - } - }, - child: const Text("Login"), - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: SizedBox( - height: 55, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: - Theme.of(context).textTheme.titleLarge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12) - ) - ), - onPressed: () { - if (formKey.currentState!.validate()) { - formEvents.mapEventsToStates( - const AuthEvents - .signUpWithEmailAndPasswordPressed(), - ); - } - }, - child: const Text("Sign Up"), - ), - ), - ), - ], + const SizedBox(height: 10), + ElevatedButton( + onPressed: formStates.isSubmitting + ? null + : () async { + if (formKey.currentState!.validate()) { + await formNotifier.signUpWithEmailAndPassword(); + } + }, + child: formStates.isSubmitting ? const CircularProgressIndicator() : const Text("Sign Up"), ), - if (formStates.isSubmitting) ...[ - const SizedBox( - height: 20, - ), - const LinearProgressIndicator( - minHeight: 6, - ) - ] ], ), ), diff --git a/lib/services/authentication/firebase_auth_facade.dart b/lib/services/authentication/firebase_auth_facade.dart index 0d1c2aa..de63593 100644 --- a/lib/services/authentication/firebase_auth_facade.dart +++ b/lib/services/authentication/firebase_auth_facade.dart @@ -1,10 +1,20 @@ import "package:firebase_auth/firebase_auth.dart"; import "package:fpdart/fpdart.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; -import "../../Domain/Authentication/auth_failures.dart"; -import "../../Domain/Authentication/auth_value_objects.dart"; -import "../../Domain/Authentication/i_auth_facade.dart"; -import "../../Domain/Core/errors.dart"; +import "../../domain/authentication/auth_failures.dart"; +import "../../domain/authentication/auth_value_objects.dart"; +import "../../domain/authentication/i_auth_facade.dart"; +import "../../domain/core/errors.dart"; + +// Manual providers without code generation +final firebaseAuthProvider = Provider<FirebaseAuth>((ref) { + return FirebaseAuth.instance; +}); + +final firebaseAuthFacadeProvider = Provider<FirebaseAuthFacade>((ref) { + return FirebaseAuthFacade(ref.read(firebaseAuthProvider)); +}); class FirebaseAuthFacade implements IAuthFacade { FirebaseAuthFacade(this._firebaseAuth); @@ -13,15 +23,11 @@ class FirebaseAuthFacade implements IAuthFacade { @override Future<Either<AuthFailures, Unit>> registerWithEmailAndPassword( - {required EmailAddress? emailAddress, - required Password? password}) async { - final emailAddressString = emailAddress!.valueObject! - .fold((l) => throw UnExpectedValueError(l), (r) => r); - final passwordString = - password!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); + {required EmailAddress? emailAddress, required Password? password}) async { + final emailAddressString = emailAddress!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); + final passwordString = password!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); try { - await _firebaseAuth.createUserWithEmailAndPassword( - email: emailAddressString, password: passwordString); + await _firebaseAuth.createUserWithEmailAndPassword(email: emailAddressString, password: passwordString); return right(unit); } on FirebaseAuthException catch (e) { if (e.code == "email-already-in-use") { @@ -34,15 +40,11 @@ class FirebaseAuthFacade implements IAuthFacade { @override Future<Either<AuthFailures, Unit>> signInWithEmailAndPassword( - {required EmailAddress? emailAddress, - required Password? password}) async { - final emailAddressString = emailAddress!.valueObject! - .fold((l) => throw UnExpectedValueError(l), (r) => r); - final passwordString = - password!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); + {required EmailAddress? emailAddress, required Password? password}) async { + final emailAddressString = emailAddress!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); + final passwordString = password!.valueObject!.fold((l) => throw UnExpectedValueError(l), (r) => r); try { - await _firebaseAuth.signInWithEmailAndPassword( - email: emailAddressString, password: passwordString); + await _firebaseAuth.signInWithEmailAndPassword(email: emailAddressString, password: passwordString); return right(unit); } on FirebaseAuthException catch (e) { if (e.code == "wrong-password" || e.code == "user-not-found") { diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..023ff7e --- /dev/null +++ b/opencode.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "task-master-ai": { + "type": "local", + "command": [ + "npx", + "-y", + "--package=task-master-ai", + "task-master-ai" + ], + "enabled": true, + "environment": { + "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", + "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", + "OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE", + "GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE", + "XAI_API_KEY": "YOUR_XAI_KEY_HERE", + "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", + "MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE", + "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", + "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE" + } + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 2d7d33d..4991bea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,98 +5,98 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: "3dee3db3468c5f4640a4e8aa9c1e22561c298976d8c39ed2fdd456a9a3db26e1" + sha256: a5788040810bd84400bc209913fbc40f388cded7cdf95ee2f5d2bff7e38d5241 url: "https://pub.dev" source: hosted - version: "1.3.32" + version: "1.3.58" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b1ade5707ab7a90dfd519eaac78a7184341d19adb6096c68d499b59c7c6cf880 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.7.0" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "3.0.0" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.0.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.6.0" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "9.2.0" built_collection: dependency: transitive description: @@ -109,66 +109,82 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.11.0" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" convert: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -181,82 +197,82 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.1.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" firebase_auth: dependency: "direct main" description: name: firebase_auth - sha256: "63b4401c95ddb00fb272872c451147e33509e80eed43937369910ef6c34c00b7" + sha256: f5b640f664aae71774b398ed765740c1b5d34a339f4c4975d4dde61d59a623f6 url: "https://pub.dev" source: hosted - version: "4.19.4" + version: "5.6.2" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "4e204f9ef43d83ac9e7a324a9317e4dd2a1ddda2aa72b67bc6cc364f0b8492dc" + sha256: "62199aeda6a688cbdefbcbbac53ede71be3ac8807cec00a8066d444797a08806" url: "https://pub.dev" source: hosted - version: "7.2.5" + version: "7.7.2" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "809a2eb444d1a07c0a680b205b86d713bc7171a4b2627fd6c01cf05f2b6f93cd" + sha256: caaf29b7eb9d212dcec36d2eaa66504c5bd523fe844302833680c9df8460fbc0 url: "https://pub.dev" source: hosted - version: "5.11.4" + version: "5.15.2" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "4aef2a23d0f3265545807d68fbc2f76a6b994ca3c778d88453b99325abd63284" + sha256: c6e8a6bf883d8ddd0dec39be90872daca65beaa6f4cff0051ed3b16c56b82e9f url: "https://pub.dev" source: hosted - version: "2.30.1" + version: "3.15.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "67f2fcc600fc78c2f731c370a3a5e6c87ee862e3a2fba6f951eca6d5dafe5c29" + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" url: "https://pub.dev" source: hosted - version: "2.16.0" + version: "2.24.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -266,26 +282,26 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "0.21.2" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "6.0.0" flutter_riverpod: dependency: transitive description: name: flutter_riverpod - sha256: "2fd9f58a39b7269cb3495b09245000fcd267243518157a7c2f832189fb64f013" + sha256: cab35f90d35d556374dd63754984e0558722894400e3d1f66fb7b9a3052e971e url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "3.0.0-dev.16" flutter_test: dependency: "direct dev" description: flutter @@ -300,26 +316,26 @@ packages: dependency: "direct main" description: name: fpdart - sha256: "7413acc5a6569a3fe8277928fc7487f3198530f0c4e635d0baef199ea36e8ee9" + sha256: "1b84ce64453974159f08046f5d05592020d1fcb2099d7fe6ec58da0e7337af77" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" freezed: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "3.2.0" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -332,154 +348,162 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - sha256: "169d7bf1d61c749c065e09388841906b2ff963794b932867372ed4f423a8d877" + sha256: f66daa922c607734af491260c1516e4a5b5f496756b4e4d0fb1aa1b529705dd7 url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "3.0.0-dev.16" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" io: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "6.0.0" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.16.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" package_config: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -500,71 +524,103 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.5.0" riverpod: dependency: transitive description: name: riverpod - sha256: "0f41a697a17609a7ac18e5fe0d5bdbe4c1ff7e7da6523baf46a203df0c44eaf2" + sha256: "0aa816ffc79f9762de33ee771f5bedd0bbc8a3dd7960f4893cfde1d18d628f31" url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "3.0.0-dev.16" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -577,58 +633,74 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.8" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -641,42 +713,58 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "15.0.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "1.2.1" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 282e879..d0fb6cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,21 +12,21 @@ environment: dependencies: cupertino_icons: ^1.0.8 - firebase_auth: ^4.19.3 - firebase_core: ^2.30.1 + firebase_auth: ^5.6.2 + firebase_core: ^3.15.1 flutter: sdk: flutter - flutter_hooks: ^0.20.5 - fpdart: ^1.1.0 - freezed_annotation: ^2.4.1 - hooks_riverpod: ^3.0.0-dev.3 + flutter_hooks: ^0.21.2 + fpdart: ^1.1.1 + freezed_annotation: ^3.1.0 + hooks_riverpod: ^3.0.0-dev.16 dev_dependencies: - build_runner: ^2.4.9 - flutter_lints: ^3.0.2 + build_runner: ^2.6.0 + flutter_lints: ^6.0.0 flutter_test: sdk: flutter - freezed: ^2.5.2 + freezed: ^3.2.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l<c?^6clWVQqrt~T->1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*<mM6;!yDy;iI!?0xZxS-@9o){@>SYAX-%d(5<QWisP% zpKxJ^!zO{CB{zx^b({)M+}nHh%-rj7^M&&ZcU$j%zjx;LVyDXoIuhq|8P;5s=KHG5 z<JUB4<A0$RmNJizixz(PY{v3_`K1HjE{oV$3+pLbhDf-dGF^GSkNIsaZ^^?aj@RFX zmI#!cuT=hV*<Wg+koNS~T)(gK8EvWbJGDN%Cgswi@;rmS$PYP}{^d1_2Ol_C#PhDD zFw$(<^hU$>gVjrHJWqXQshj@!<B9Fv+a9JEO;0$m^uEygb*U`#RIBf=|21JrRHoLF zSVqsI&*oWt$jm+Fz?pMwCX>Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..79b3691 --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> + <!-- + If you are serving your web app in a path other than the root, change the + href value below to reflect the base path you are serving from. + + The path provided below has to start and end with a slash "/" in order for + it to work correctly. + + For more details: + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. + --> + <base href="$FLUTTER_BASE_HREF"> + + <meta charset="UTF-8"> + <meta content="IE=Edge" http-equiv="X-UA-Compatible"> + <meta name="description" content="A new Flutter project."> + + <!-- iOS meta tags & icons --> + <meta name="mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-status-bar-style" content="black"> + <meta name="apple-mobile-web-app-title" content="firebase_auth_flutter_ddd"> + <link rel="apple-touch-icon" href="icons/Icon-192.png"> + + <!-- Favicon --> + <link rel="icon" type="image/png" href="favicon.png"/> + + <title>firebase_auth_flutter_ddd + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..15906a9 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "firebase_auth_flutter_ddd", + "short_name": "firebase_auth_flutter_ddd", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}