moai-adk 0.35.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +10 -0
- moai_adk/__main__.py +199 -0
- moai_adk/cli/__init__.py +6 -0
- moai_adk/cli/commands/__init__.py +17 -0
- moai_adk/cli/commands/analyze.py +116 -0
- moai_adk/cli/commands/doctor.py +272 -0
- moai_adk/cli/commands/init.py +372 -0
- moai_adk/cli/commands/language.py +248 -0
- moai_adk/cli/commands/status.py +104 -0
- moai_adk/cli/commands/update.py +2686 -0
- moai_adk/cli/main.py +13 -0
- moai_adk/cli/prompts/__init__.py +5 -0
- moai_adk/cli/prompts/init_prompts.py +219 -0
- moai_adk/cli/spec_status.py +263 -0
- moai_adk/cli/ui/__init__.py +44 -0
- moai_adk/cli/ui/progress.py +422 -0
- moai_adk/cli/ui/prompts.py +389 -0
- moai_adk/cli/ui/theme.py +129 -0
- moai_adk/cli/worktree/__init__.py +27 -0
- moai_adk/cli/worktree/__main__.py +31 -0
- moai_adk/cli/worktree/cli.py +683 -0
- moai_adk/cli/worktree/exceptions.py +89 -0
- moai_adk/cli/worktree/manager.py +493 -0
- moai_adk/cli/worktree/models.py +65 -0
- moai_adk/cli/worktree/registry.py +422 -0
- moai_adk/core/PHASE2_OPTIMIZATIONS.md +467 -0
- moai_adk/core/__init__.py +1 -0
- moai_adk/core/analysis/__init__.py +9 -0
- moai_adk/core/analysis/session_analyzer.py +400 -0
- moai_adk/core/claude_integration.py +393 -0
- moai_adk/core/command_helpers.py +270 -0
- moai_adk/core/comprehensive_monitoring_system.py +1183 -0
- moai_adk/core/config/__init__.py +19 -0
- moai_adk/core/config/auto_spec_config.py +340 -0
- moai_adk/core/config/migration.py +244 -0
- moai_adk/core/config/unified.py +436 -0
- moai_adk/core/context_manager.py +273 -0
- moai_adk/core/diagnostics/__init__.py +19 -0
- moai_adk/core/diagnostics/slash_commands.py +159 -0
- moai_adk/core/enterprise_features.py +1404 -0
- moai_adk/core/error_recovery_system.py +1902 -0
- moai_adk/core/event_driven_hook_system.py +1371 -0
- moai_adk/core/git/__init__.py +31 -0
- moai_adk/core/git/branch.py +25 -0
- moai_adk/core/git/branch_manager.py +129 -0
- moai_adk/core/git/checkpoint.py +134 -0
- moai_adk/core/git/commit.py +67 -0
- moai_adk/core/git/conflict_detector.py +413 -0
- moai_adk/core/git/event_detector.py +79 -0
- moai_adk/core/git/manager.py +216 -0
- moai_adk/core/hooks/post_tool_auto_spec_completion.py +901 -0
- moai_adk/core/input_validation_middleware.py +1006 -0
- moai_adk/core/integration/__init__.py +22 -0
- moai_adk/core/integration/engine.py +157 -0
- moai_adk/core/integration/integration_tester.py +226 -0
- moai_adk/core/integration/models.py +88 -0
- moai_adk/core/integration/utils.py +211 -0
- moai_adk/core/issue_creator.py +305 -0
- moai_adk/core/jit_context_loader.py +956 -0
- moai_adk/core/jit_enhanced_hook_manager.py +1987 -0
- moai_adk/core/language_config.py +202 -0
- moai_adk/core/language_config_resolver.py +572 -0
- moai_adk/core/language_validator.py +543 -0
- moai_adk/core/mcp/setup.py +116 -0
- moai_adk/core/merge/__init__.py +9 -0
- moai_adk/core/merge/analyzer.py +605 -0
- moai_adk/core/migration/__init__.py +18 -0
- moai_adk/core/migration/alfred_to_moai_migrator.py +383 -0
- moai_adk/core/migration/backup_manager.py +277 -0
- moai_adk/core/migration/custom_element_scanner.py +358 -0
- moai_adk/core/migration/file_migrator.py +209 -0
- moai_adk/core/migration/interactive_checkbox_ui.py +488 -0
- moai_adk/core/migration/selective_restorer.py +470 -0
- moai_adk/core/migration/template_utils.py +74 -0
- moai_adk/core/migration/user_selection_ui.py +338 -0
- moai_adk/core/migration/version_detector.py +139 -0
- moai_adk/core/migration/version_migrator.py +228 -0
- moai_adk/core/performance/__init__.py +6 -0
- moai_adk/core/performance/cache_system.py +316 -0
- moai_adk/core/performance/parallel_processor.py +116 -0
- moai_adk/core/phase_optimized_hook_scheduler.py +879 -0
- moai_adk/core/project/__init__.py +1 -0
- moai_adk/core/project/backup_utils.py +70 -0
- moai_adk/core/project/checker.py +300 -0
- moai_adk/core/project/detector.py +293 -0
- moai_adk/core/project/initializer.py +387 -0
- moai_adk/core/project/phase_executor.py +716 -0
- moai_adk/core/project/validator.py +139 -0
- moai_adk/core/quality/__init__.py +6 -0
- moai_adk/core/quality/trust_checker.py +377 -0
- moai_adk/core/quality/validators/__init__.py +6 -0
- moai_adk/core/quality/validators/base_validator.py +19 -0
- moai_adk/core/realtime_monitoring_dashboard.py +1724 -0
- moai_adk/core/robust_json_parser.py +611 -0
- moai_adk/core/rollback_manager.py +918 -0
- moai_adk/core/session_manager.py +651 -0
- moai_adk/core/skill_loading_system.py +579 -0
- moai_adk/core/spec/confidence_scoring.py +680 -0
- moai_adk/core/spec/ears_template_engine.py +1247 -0
- moai_adk/core/spec/quality_validator.py +687 -0
- moai_adk/core/spec_status_manager.py +478 -0
- moai_adk/core/template/__init__.py +7 -0
- moai_adk/core/template/backup.py +174 -0
- moai_adk/core/template/config.py +191 -0
- moai_adk/core/template/languages.py +43 -0
- moai_adk/core/template/merger.py +233 -0
- moai_adk/core/template/processor.py +1200 -0
- moai_adk/core/template_engine.py +310 -0
- moai_adk/core/template_variable_synchronizer.py +417 -0
- moai_adk/core/unified_permission_manager.py +745 -0
- moai_adk/core/user_behavior_analytics.py +851 -0
- moai_adk/core/version_sync.py +429 -0
- moai_adk/foundation/__init__.py +56 -0
- moai_adk/foundation/backend.py +1027 -0
- moai_adk/foundation/database.py +1115 -0
- moai_adk/foundation/devops.py +1585 -0
- moai_adk/foundation/ears.py +431 -0
- moai_adk/foundation/frontend.py +870 -0
- moai_adk/foundation/git/commit_templates.py +557 -0
- moai_adk/foundation/git.py +376 -0
- moai_adk/foundation/langs.py +484 -0
- moai_adk/foundation/ml_ops.py +1162 -0
- moai_adk/foundation/testing.py +1524 -0
- moai_adk/foundation/trust/trust_principles.py +676 -0
- moai_adk/foundation/trust/validation_checklist.py +1573 -0
- moai_adk/project/__init__.py +0 -0
- moai_adk/project/configuration.py +1084 -0
- moai_adk/project/documentation.py +566 -0
- moai_adk/project/schema.py +447 -0
- moai_adk/statusline/__init__.py +38 -0
- moai_adk/statusline/alfred_detector.py +105 -0
- moai_adk/statusline/config.py +376 -0
- moai_adk/statusline/enhanced_output_style_detector.py +372 -0
- moai_adk/statusline/git_collector.py +190 -0
- moai_adk/statusline/main.py +322 -0
- moai_adk/statusline/metrics_tracker.py +78 -0
- moai_adk/statusline/renderer.py +343 -0
- moai_adk/statusline/update_checker.py +129 -0
- moai_adk/statusline/version_reader.py +741 -0
- moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +714 -0
- moai_adk/templates/.claude/agents/moai/builder-agent.md +474 -0
- moai_adk/templates/.claude/agents/moai/builder-command.md +1172 -0
- moai_adk/templates/.claude/agents/moai/builder-plugin.md +637 -0
- moai_adk/templates/.claude/agents/moai/builder-skill.md +666 -0
- moai_adk/templates/.claude/agents/moai/expert-backend.md +899 -0
- moai_adk/templates/.claude/agents/moai/expert-database.md +777 -0
- moai_adk/templates/.claude/agents/moai/expert-debug.md +401 -0
- moai_adk/templates/.claude/agents/moai/expert-devops.md +720 -0
- moai_adk/templates/.claude/agents/moai/expert-frontend.md +734 -0
- moai_adk/templates/.claude/agents/moai/expert-performance.md +657 -0
- moai_adk/templates/.claude/agents/moai/expert-security.md +513 -0
- moai_adk/templates/.claude/agents/moai/expert-testing.md +733 -0
- moai_adk/templates/.claude/agents/moai/expert-uiux.md +1041 -0
- moai_adk/templates/.claude/agents/moai/manager-claude-code.md +432 -0
- moai_adk/templates/.claude/agents/moai/manager-docs.md +573 -0
- moai_adk/templates/.claude/agents/moai/manager-git.md +1060 -0
- moai_adk/templates/.claude/agents/moai/manager-project.md +891 -0
- moai_adk/templates/.claude/agents/moai/manager-quality.md +624 -0
- moai_adk/templates/.claude/agents/moai/manager-spec.md +809 -0
- moai_adk/templates/.claude/agents/moai/manager-strategy.md +780 -0
- moai_adk/templates/.claude/agents/moai/manager-tdd.md +784 -0
- moai_adk/templates/.claude/agents/moai/mcp-context7.md +458 -0
- moai_adk/templates/.claude/agents/moai/mcp-figma.md +1607 -0
- moai_adk/templates/.claude/agents/moai/mcp-notion.md +789 -0
- moai_adk/templates/.claude/agents/moai/mcp-playwright.md +469 -0
- moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +1032 -0
- moai_adk/templates/.claude/commands/moai/0-project.md +1386 -0
- moai_adk/templates/.claude/commands/moai/1-plan.md +1427 -0
- moai_adk/templates/.claude/commands/moai/2-run.md +943 -0
- moai_adk/templates/.claude/commands/moai/3-sync.md +1324 -0
- moai_adk/templates/.claude/commands/moai/9-feedback.md +314 -0
- moai_adk/templates/.claude/hooks/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/__init__.py +8 -0
- moai_adk/templates/.claude/hooks/moai/lib/__init__.py +85 -0
- moai_adk/templates/.claude/hooks/moai/lib/checkpoint.py +244 -0
- moai_adk/templates/.claude/hooks/moai/lib/common.py +131 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_manager.py +446 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_validator.py +639 -0
- moai_adk/templates/.claude/hooks/moai/lib/example_config.json +104 -0
- moai_adk/templates/.claude/hooks/moai/lib/git_operations_manager.py +590 -0
- moai_adk/templates/.claude/hooks/moai/lib/language_validator.py +317 -0
- moai_adk/templates/.claude/hooks/moai/lib/models.py +102 -0
- moai_adk/templates/.claude/hooks/moai/lib/path_utils.py +28 -0
- moai_adk/templates/.claude/hooks/moai/lib/project.py +768 -0
- moai_adk/templates/.claude/hooks/moai/lib/test_hooks_improvements.py +443 -0
- moai_adk/templates/.claude/hooks/moai/lib/timeout.py +160 -0
- moai_adk/templates/.claude/hooks/moai/lib/unified_timeout_manager.py +530 -0
- moai_adk/templates/.claude/hooks/moai/session_end__auto_cleanup.py +862 -0
- moai_adk/templates/.claude/hooks/moai/session_start__show_project_info.py +1083 -0
- moai_adk/templates/.claude/output-styles/moai/r2d2.md +560 -0
- moai_adk/templates/.claude/output-styles/moai/yoda.md +359 -0
- moai_adk/templates/.claude/settings.json +172 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +307 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +431 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/batch_generate.py +560 -0
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/scripts/generate_image.py +362 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +249 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/examples.md +406 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/README.md +44 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/api-documentation.md +130 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/code-documentation.md +152 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/multi-format-output.md +178 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/user-guides.md +147 -0
- moai_adk/templates/.claude/skills/moai-docs-generation/reference.md +328 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +320 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +718 -0
- moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +464 -0
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +323 -0
- moai_adk/templates/.claude/skills/moai-domain-database/examples.md +830 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/README.md +53 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/mongodb.md +231 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/postgresql.md +169 -0
- moai_adk/templates/.claude/skills/moai-domain-database/modules/redis.md +262 -0
- moai_adk/templates/.claude/skills/moai-domain-database/reference.md +545 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +968 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +664 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +455 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/examples.md +560 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/accessibility-wcag.md +260 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/component-architecture.md +228 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/icon-libraries.md +401 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/theming-system.md +373 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/reference.md +243 -0
- moai_adk/templates/.claude/skills/moai-formats-data/SKILL.md +492 -0
- moai_adk/templates/.claude/skills/moai-formats-data/examples.md +804 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/README.md +98 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/SKILL-MODULARIZATION-TEMPLATE.md +278 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/caching-performance.md +459 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/data-validation.md +485 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/json-optimization.md +374 -0
- moai_adk/templates/.claude/skills/moai-formats-data/modules/toon-encoding.md +308 -0
- moai_adk/templates/.claude/skills/moai-formats-data/reference.md +585 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +202 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/examples.md +732 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/best-practices-checklist.md +616 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-custom-slash-commands-official.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-hooks-official.md +560 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-iam-official.md +635 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-memory-official.md +543 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-settings-official.md +663 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-skills-official.md +113 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sub-agents-official.md +238 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/complete-configuration-guide.md +175 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-examples.md +1674 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/skill-formatting-guide.md +729 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-examples.md +1513 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-formatting-guide.md +1086 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/sub-agents/sub-agent-integration-patterns.md +1100 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference.md +209 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +441 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/examples.md +1048 -0
- moai_adk/templates/.claude/skills/moai-foundation-context/reference.md +246 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +420 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/examples.md +358 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/README.md +296 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/agents-reference.md +359 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/commands-reference.md +432 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-patterns.md +757 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/execution-rules.md +687 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/modular-system.md +665 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/progressive-disclosure.md +649 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-first-tdd.md +864 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/token-optimization.md +708 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-framework.md +981 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/reference.md +478 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/SKILL.md +315 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/examples.md +228 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/assumption-matrix.md +80 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/cognitive-bias.md +199 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/first-principles.md +140 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/modules/trade-off-analysis.md +154 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/reference.md +157 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +364 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/examples.md +1232 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/best-practices.md +261 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/integration-patterns.md +194 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/proactive-analysis.md +229 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/modules/trust5-validation.md +169 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/reference.md +1266 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/scripts/quality-gate.sh +668 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/github-actions-quality.yml +481 -0
- moai_adk/templates/.claude/skills/moai-foundation-quality/templates/quality-config.yaml +519 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +649 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +478 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +612 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/SKILL.md +477 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/examples.md +1090 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/reference.md +686 -0
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +376 -0
- moai_adk/templates/.claude/skills/moai-lang-go/examples.md +919 -0
- moai_adk/templates/.claude/skills/moai-lang-go/reference.md +737 -0
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +385 -0
- moai_adk/templates/.claude/skills/moai-lang-java/examples.md +864 -0
- moai_adk/templates/.claude/skills/moai-lang-java/reference.md +291 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +382 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +1006 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +562 -0
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +644 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +481 -0
- moai_adk/templates/.claude/skills/moai-lang-python/examples.md +977 -0
- moai_adk/templates/.claude/skills/moai-lang-python/reference.md +804 -0
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +579 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +687 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +372 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +659 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +504 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +633 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +423 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +497 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +918 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +672 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +368 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +1089 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +731 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +300 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/advanced-patterns.md +465 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/examples.md +270 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/optimization.md +440 -0
- moai_adk/templates/.claude/skills/moai-library-mermaid/reference.md +228 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/SKILL.md +319 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +336 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/examples.md +592 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-deployment-patterns.md +182 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-patterns.md +17 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/configuration.md +57 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/content-architecture-optimization.md +162 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/deployment.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/framework-core-configuration.md +186 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/i18n-setup.md +55 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/mdx-components.md +52 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/optimization.md +303 -0
- moai_adk/templates/.claude/skills/moai-library-nextra/reference.md +379 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +372 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/examples.md +575 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/advanced-patterns.md +394 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/optimization.md +278 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-components.md +457 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/modules/shadcn-theming.md +373 -0
- moai_adk/templates/.claude/skills/moai-library-shadcn/reference.md +74 -0
- moai_adk/templates/.claude/skills/moai-mcp-figma/SKILL.md +402 -0
- moai_adk/templates/.claude/skills/moai-mcp-figma/advanced-patterns.md +607 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/SKILL.md +300 -0
- moai_adk/templates/.claude/skills/moai-mcp-notion/advanced-patterns.md +537 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/SKILL.md +291 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/SKILL.md +390 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/SKILL.md +398 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/SKILL.md +379 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/SKILL.md +358 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/SKILL.md +467 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/SKILL.md +466 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/SKILL.md +482 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/SKILL.md +474 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/examples.md +621 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/migration.md +341 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/reference.md +463 -0
- moai_adk/templates/.claude/skills/moai-plugin-builder/validation.md +373 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/SKILL.md +275 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/adaptive-mfa.md +233 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/akamai-integration.md +215 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/application-credentials.md +280 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-log-events.md +225 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/attack-protection-overview.md +140 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/bot-detection.md +144 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/breached-password-detection.md +187 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/brute-force-protection.md +189 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/certifications.md +282 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/compliance-overview.md +263 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/continuous-session-protection.md +307 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/customize-mfa.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/dpop-implementation.md +283 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/fapi-implementation.md +259 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/gdpr-compliance.md +313 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/guardian-configuration.md +269 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/highly-regulated-identity.md +272 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/jwt-fundamentals.md +248 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mdl-verification.md +211 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-api-management.md +278 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-factors.md +226 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mfa-overview.md +174 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/mtls-sender-constraining.md +316 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/ropg-flow-mfa.md +217 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-center.md +325 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/security-guidance.md +277 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/state-parameters.md +178 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/step-up-authentication.md +251 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/suspicious-ip-throttling.md +240 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/tenant-access-control.md +180 -0
- moai_adk/templates/.claude/skills/moai-security-auth0/modules/webauthn-fido.md +235 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +449 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/advanced-patterns.md +379 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/examples.md +544 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/optimization.md +286 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/reference.md +307 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/README.md +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +390 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/__init__.py +520 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/complete_workflow_demo_fixed.py +574 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_project_setup.py +317 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_workflow_demo.py +663 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/config-migration-example.json +190 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/question-examples.json +175 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +196 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/examples.md +547 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/__init__.py +17 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/advanced-patterns.md +158 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/ask_user_integration.py +340 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/batch_questions.py +713 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/config_manager.py +538 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/documentation_manager.py +1336 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/language_initializer.py +730 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/migration_manager.py +608 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/template_optimizer.py +1005 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/reference.md +275 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/config-schema.json +316 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/tab_schema.json +1434 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/config-template.json +71 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/product-template.md +44 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/structure-template.md +48 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/doc-templates/tech-template.md +92 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/config-manager-setup.json +109 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/language-initializer.json +228 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/menu-project-config.json +130 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/project-batch-questions.json +97 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/templates/question-templates/spec-workflow-setup.json +150 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/test_integration_simple.py +436 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/SKILL.md +534 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/examples.md +900 -0
- moai_adk/templates/.claude/skills/moai-workflow-spec/reference.md +704 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +377 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/examples.md +552 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/code-templates.md +124 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/feedback-templates.md +100 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/modules/template-optimizer.md +138 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/reference.md +346 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/LICENSE.txt +202 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +456 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/advanced-patterns.md +576 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/ai-powered-testing.py +294 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/console_logging.py +35 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/element_discovery.py +40 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples/static_html_automation.py +34 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/examples.md +672 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/README.md +220 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/ai-debugging.md +845 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review.md +1416 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization.md +1234 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/smart-refactoring.md +1243 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7.md +1260 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/optimization.md +505 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/reference/playwright-best-practices.md +57 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/reference.md +440 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/scripts/with_server.py +218 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/templates/alfred-integration.md +376 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/workflows/enterprise-testing-workflow.py +571 -0
- moai_adk/templates/.claude/skills/moai-worktree/SKILL.md +411 -0
- moai_adk/templates/.claude/skills/moai-worktree/examples.md +606 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/integration-patterns.md +982 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/parallel-development.md +778 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-commands.md +646 -0
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-management.md +782 -0
- moai_adk/templates/.claude/skills/moai-worktree/reference.md +357 -0
- moai_adk/templates/.git-hooks/pre-commit +128 -0
- moai_adk/templates/.git-hooks/pre-push +365 -0
- moai_adk/templates/.github/workflows/ci-universal.yml +513 -0
- moai_adk/templates/.github/workflows/security-secrets-check.yml +179 -0
- moai_adk/templates/.github/workflows/spec-issue-sync.yml +337 -0
- moai_adk/templates/.gitignore +222 -0
- moai_adk/templates/.mcp.json +13 -0
- moai_adk/templates/.moai/config/config.yaml +58 -0
- moai_adk/templates/.moai/config/questions/_schema.yaml +174 -0
- moai_adk/templates/.moai/config/questions/tab0-init.yaml +251 -0
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +107 -0
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +79 -0
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +632 -0
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +182 -0
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +96 -0
- moai_adk/templates/.moai/config/sections/git-strategy.yaml +116 -0
- moai_adk/templates/.moai/config/sections/language.yaml +11 -0
- moai_adk/templates/.moai/config/sections/project.yaml +13 -0
- moai_adk/templates/.moai/config/sections/quality.yaml +17 -0
- moai_adk/templates/.moai/config/sections/system.yaml +24 -0
- moai_adk/templates/.moai/config/sections/user.yaml +5 -0
- moai_adk/templates/.moai/config/statusline-config.yaml +92 -0
- moai_adk/templates/.moai/scripts/setup-glm.py +136 -0
- moai_adk/templates/CLAUDE.md +642 -0
- moai_adk/utils/__init__.py +30 -0
- moai_adk/utils/banner.py +38 -0
- moai_adk/utils/common.py +294 -0
- moai_adk/utils/link_validator.py +241 -0
- moai_adk/utils/logger.py +147 -0
- moai_adk/utils/safe_file_reader.py +206 -0
- moai_adk/utils/timeout.py +160 -0
- moai_adk/utils/toon_utils.py +256 -0
- moai_adk/version.py +22 -0
- moai_adk-0.35.1.dist-info/METADATA +3018 -0
- moai_adk-0.35.1.dist-info/RECORD +502 -0
- moai_adk-0.35.1.dist-info/WHEEL +4 -0
- moai_adk-0.35.1.dist-info/entry_points.txt +3 -0
- moai_adk-0.35.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
# Flutter/Dart Examples
|
|
2
|
+
|
|
3
|
+
Production-ready code examples for Flutter cross-platform development.
|
|
4
|
+
|
|
5
|
+
## Complete Feature: User Authentication
|
|
6
|
+
|
|
7
|
+
### Full Authentication Flow with Riverpod
|
|
8
|
+
|
|
9
|
+
```dart
|
|
10
|
+
// lib/features/auth/domain/entities/user.dart
|
|
11
|
+
class User {
|
|
12
|
+
final String id;
|
|
13
|
+
final String email;
|
|
14
|
+
final String name;
|
|
15
|
+
final String? avatarUrl;
|
|
16
|
+
|
|
17
|
+
const User({
|
|
18
|
+
required this.id,
|
|
19
|
+
required this.email,
|
|
20
|
+
required this.name,
|
|
21
|
+
this.avatarUrl,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class AuthTokens {
|
|
26
|
+
final String accessToken;
|
|
27
|
+
final String refreshToken;
|
|
28
|
+
final DateTime expiresAt;
|
|
29
|
+
|
|
30
|
+
const AuthTokens({
|
|
31
|
+
required this.accessToken,
|
|
32
|
+
required this.refreshToken,
|
|
33
|
+
required this.expiresAt,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
bool get isExpired => DateTime.now().isAfter(expiresAt);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// lib/features/auth/domain/errors/auth_error.dart
|
|
40
|
+
sealed class AuthError implements Exception {
|
|
41
|
+
const AuthError();
|
|
42
|
+
|
|
43
|
+
String get message => switch (this) {
|
|
44
|
+
InvalidCredentialsError() => 'Invalid email or password',
|
|
45
|
+
NetworkError(:final cause) => 'Network error: $cause',
|
|
46
|
+
TokenExpiredError() => 'Session expired. Please login again',
|
|
47
|
+
UnauthorizedError() => 'Unauthorized access',
|
|
48
|
+
UnknownError(:final message) => message,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class InvalidCredentialsError extends AuthError {
|
|
53
|
+
const InvalidCredentialsError();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class NetworkError extends AuthError {
|
|
57
|
+
final Object cause;
|
|
58
|
+
const NetworkError(this.cause);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class TokenExpiredError extends AuthError {
|
|
62
|
+
const TokenExpiredError();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class UnauthorizedError extends AuthError {
|
|
66
|
+
const UnauthorizedError();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class UnknownError extends AuthError {
|
|
70
|
+
@override
|
|
71
|
+
final String message;
|
|
72
|
+
const UnknownError(this.message);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// lib/features/auth/domain/repositories/auth_repository.dart
|
|
76
|
+
abstract class AuthRepository {
|
|
77
|
+
Future<User> login(String email, String password);
|
|
78
|
+
Future<void> logout();
|
|
79
|
+
Future<User?> restoreSession();
|
|
80
|
+
Stream<User?> watchAuthState();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// lib/features/auth/data/models/user_model.dart
|
|
84
|
+
class UserModel extends User {
|
|
85
|
+
const UserModel({
|
|
86
|
+
required super.id,
|
|
87
|
+
required super.email,
|
|
88
|
+
required super.name,
|
|
89
|
+
super.avatarUrl,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
factory UserModel.fromJson(Map<String, dynamic> json) => UserModel(
|
|
93
|
+
id: json['id'] as String,
|
|
94
|
+
email: json['email'] as String,
|
|
95
|
+
name: json['name'] as String,
|
|
96
|
+
avatarUrl: json['avatar_url'] as String?,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
Map<String, dynamic> toJson() => {
|
|
100
|
+
'id': id,
|
|
101
|
+
'email': email,
|
|
102
|
+
'name': name,
|
|
103
|
+
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class AuthTokensModel extends AuthTokens {
|
|
108
|
+
const AuthTokensModel({
|
|
109
|
+
required super.accessToken,
|
|
110
|
+
required super.refreshToken,
|
|
111
|
+
required super.expiresAt,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
factory AuthTokensModel.fromJson(Map<String, dynamic> json) => AuthTokensModel(
|
|
115
|
+
accessToken: json['access_token'] as String,
|
|
116
|
+
refreshToken: json['refresh_token'] as String,
|
|
117
|
+
expiresAt: DateTime.parse(json['expires_at'] as String),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
Map<String, dynamic> toJson() => {
|
|
121
|
+
'access_token': accessToken,
|
|
122
|
+
'refresh_token': refreshToken,
|
|
123
|
+
'expires_at': expiresAt.toIso8601String(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// lib/features/auth/data/datasources/auth_api.dart
|
|
128
|
+
class AuthApi {
|
|
129
|
+
final Dio _dio;
|
|
130
|
+
|
|
131
|
+
AuthApi(this._dio);
|
|
132
|
+
|
|
133
|
+
Future<AuthTokensModel> login(String email, String password) async {
|
|
134
|
+
final response = await _dio.post('/auth/login', data: {
|
|
135
|
+
'email': email,
|
|
136
|
+
'password': password,
|
|
137
|
+
});
|
|
138
|
+
return AuthTokensModel.fromJson(response.data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Future<AuthTokensModel> refreshToken(String refreshToken) async {
|
|
142
|
+
final response = await _dio.post('/auth/refresh', data: {
|
|
143
|
+
'refresh_token': refreshToken,
|
|
144
|
+
});
|
|
145
|
+
return AuthTokensModel.fromJson(response.data);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Future<UserModel> getUser(String accessToken) async {
|
|
149
|
+
final response = await _dio.get(
|
|
150
|
+
'/auth/me',
|
|
151
|
+
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
|
|
152
|
+
);
|
|
153
|
+
return UserModel.fromJson(response.data);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Future<void> logout(String accessToken) async {
|
|
157
|
+
await _dio.post(
|
|
158
|
+
'/auth/logout',
|
|
159
|
+
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// lib/features/auth/data/datasources/secure_storage.dart
|
|
165
|
+
class SecureStorage {
|
|
166
|
+
static const _tokensKey = 'auth_tokens';
|
|
167
|
+
final FlutterSecureStorage _storage;
|
|
168
|
+
|
|
169
|
+
SecureStorage(this._storage);
|
|
170
|
+
|
|
171
|
+
Future<AuthTokensModel?> getTokens() async {
|
|
172
|
+
final json = await _storage.read(key: _tokensKey);
|
|
173
|
+
if (json == null) return null;
|
|
174
|
+
return AuthTokensModel.fromJson(jsonDecode(json));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
Future<void> saveTokens(AuthTokensModel tokens) async {
|
|
178
|
+
await _storage.write(
|
|
179
|
+
key: _tokensKey,
|
|
180
|
+
value: jsonEncode(tokens.toJson()),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Future<void> clearTokens() async {
|
|
185
|
+
await _storage.delete(key: _tokensKey);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// lib/features/auth/data/repositories/auth_repository_impl.dart
|
|
190
|
+
class AuthRepositoryImpl implements AuthRepository {
|
|
191
|
+
final AuthApi _api;
|
|
192
|
+
final SecureStorage _storage;
|
|
193
|
+
final _authStateController = StreamController<User?>.broadcast();
|
|
194
|
+
|
|
195
|
+
AuthRepositoryImpl(this._api, this._storage);
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
Stream<User?> watchAuthState() => _authStateController.stream;
|
|
199
|
+
|
|
200
|
+
@override
|
|
201
|
+
Future<User> login(String email, String password) async {
|
|
202
|
+
try {
|
|
203
|
+
final tokens = await _api.login(email, password);
|
|
204
|
+
await _storage.saveTokens(tokens);
|
|
205
|
+
|
|
206
|
+
final user = await _api.getUser(tokens.accessToken);
|
|
207
|
+
_authStateController.add(user);
|
|
208
|
+
|
|
209
|
+
return user;
|
|
210
|
+
} on DioException catch (e) {
|
|
211
|
+
throw _mapDioError(e);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@override
|
|
216
|
+
Future<void> logout() async {
|
|
217
|
+
try {
|
|
218
|
+
final tokens = await _storage.getTokens();
|
|
219
|
+
if (tokens != null) {
|
|
220
|
+
await _api.logout(tokens.accessToken).catchError((_) {});
|
|
221
|
+
}
|
|
222
|
+
} finally {
|
|
223
|
+
await _storage.clearTokens();
|
|
224
|
+
_authStateController.add(null);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@override
|
|
229
|
+
Future<User?> restoreSession() async {
|
|
230
|
+
final tokens = await _storage.getTokens();
|
|
231
|
+
if (tokens == null) return null;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
AuthTokensModel activeTokens = tokens;
|
|
235
|
+
|
|
236
|
+
if (tokens.isExpired) {
|
|
237
|
+
activeTokens = await _api.refreshToken(tokens.refreshToken);
|
|
238
|
+
await _storage.saveTokens(activeTokens);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
final user = await _api.getUser(activeTokens.accessToken);
|
|
242
|
+
_authStateController.add(user);
|
|
243
|
+
return user;
|
|
244
|
+
} on DioException catch (e) {
|
|
245
|
+
if (e.response?.statusCode == 401) {
|
|
246
|
+
await _storage.clearTokens();
|
|
247
|
+
_authStateController.add(null);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
throw _mapDioError(e);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
AuthError _mapDioError(DioException e) {
|
|
255
|
+
if (e.type == DioExceptionType.connectionError ||
|
|
256
|
+
e.type == DioExceptionType.connectionTimeout) {
|
|
257
|
+
return NetworkError(e);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
final statusCode = e.response?.statusCode;
|
|
261
|
+
return switch (statusCode) {
|
|
262
|
+
401 => const InvalidCredentialsError(),
|
|
263
|
+
403 => const UnauthorizedError(),
|
|
264
|
+
_ => UnknownError(e.message ?? 'Unknown error'),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// lib/features/auth/presentation/providers/auth_provider.dart
|
|
270
|
+
part 'auth_provider.g.dart';
|
|
271
|
+
|
|
272
|
+
@riverpod
|
|
273
|
+
AuthRepository authRepository(Ref ref) {
|
|
274
|
+
return AuthRepositoryImpl(
|
|
275
|
+
ref.read(authApiProvider),
|
|
276
|
+
ref.read(secureStorageProvider),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@riverpod
|
|
281
|
+
Stream<User?> authState(Ref ref) {
|
|
282
|
+
return ref.watch(authRepositoryProvider).watchAuthState();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@riverpod
|
|
286
|
+
class AuthController extends _$AuthController {
|
|
287
|
+
@override
|
|
288
|
+
FutureOr<User?> build() async {
|
|
289
|
+
return ref.read(authRepositoryProvider).restoreSession();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Future<void> login(String email, String password) async {
|
|
293
|
+
state = const AsyncLoading();
|
|
294
|
+
state = await AsyncValue.guard(
|
|
295
|
+
() => ref.read(authRepositoryProvider).login(email, password),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
Future<void> logout() async {
|
|
300
|
+
await ref.read(authRepositoryProvider).logout();
|
|
301
|
+
state = const AsyncData(null);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// lib/features/auth/presentation/screens/login_screen.dart
|
|
306
|
+
class LoginScreen extends ConsumerStatefulWidget {
|
|
307
|
+
const LoginScreen({super.key});
|
|
308
|
+
|
|
309
|
+
@override
|
|
310
|
+
ConsumerState<LoginScreen> createState() => _LoginScreenState();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|
314
|
+
final _formKey = GlobalKey<FormState>();
|
|
315
|
+
final _emailController = TextEditingController();
|
|
316
|
+
final _passwordController = TextEditingController();
|
|
317
|
+
bool _obscurePassword = true;
|
|
318
|
+
|
|
319
|
+
@override
|
|
320
|
+
void dispose() {
|
|
321
|
+
_emailController.dispose();
|
|
322
|
+
_passwordController.dispose();
|
|
323
|
+
super.dispose();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
Future<void> _handleLogin() async {
|
|
327
|
+
if (!_formKey.currentState!.validate()) return;
|
|
328
|
+
|
|
329
|
+
await ref.read(authControllerProvider.notifier).login(
|
|
330
|
+
_emailController.text.trim(),
|
|
331
|
+
_passwordController.text,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@override
|
|
336
|
+
Widget build(BuildContext context) {
|
|
337
|
+
final authState = ref.watch(authControllerProvider);
|
|
338
|
+
final theme = Theme.of(context);
|
|
339
|
+
|
|
340
|
+
// Listen for auth state changes
|
|
341
|
+
ref.listen(authControllerProvider, (prev, next) {
|
|
342
|
+
next.whenOrNull(
|
|
343
|
+
data: (user) {
|
|
344
|
+
if (user != null) {
|
|
345
|
+
context.go('/home');
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
error: (error, _) {
|
|
349
|
+
final message = error is AuthError
|
|
350
|
+
? error.message
|
|
351
|
+
: 'An unexpected error occurred';
|
|
352
|
+
|
|
353
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
354
|
+
SnackBar(
|
|
355
|
+
content: Text(message),
|
|
356
|
+
backgroundColor: theme.colorScheme.error,
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
},
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return Scaffold(
|
|
364
|
+
body: SafeArea(
|
|
365
|
+
child: SingleChildScrollView(
|
|
366
|
+
padding: const EdgeInsets.all(24),
|
|
367
|
+
child: Form(
|
|
368
|
+
key: _formKey,
|
|
369
|
+
child: Column(
|
|
370
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
371
|
+
children: [
|
|
372
|
+
const SizedBox(height: 48),
|
|
373
|
+
|
|
374
|
+
// Header
|
|
375
|
+
Text(
|
|
376
|
+
'Welcome Back',
|
|
377
|
+
style: theme.textTheme.headlineLarge,
|
|
378
|
+
textAlign: TextAlign.center,
|
|
379
|
+
),
|
|
380
|
+
const SizedBox(height: 8),
|
|
381
|
+
Text(
|
|
382
|
+
'Sign in to continue',
|
|
383
|
+
style: theme.textTheme.bodyLarge?.copyWith(
|
|
384
|
+
color: theme.colorScheme.onSurfaceVariant,
|
|
385
|
+
),
|
|
386
|
+
textAlign: TextAlign.center,
|
|
387
|
+
),
|
|
388
|
+
const SizedBox(height: 48),
|
|
389
|
+
|
|
390
|
+
// Email field
|
|
391
|
+
TextFormField(
|
|
392
|
+
controller: _emailController,
|
|
393
|
+
keyboardType: TextInputType.emailAddress,
|
|
394
|
+
textInputAction: TextInputAction.next,
|
|
395
|
+
decoration: const InputDecoration(
|
|
396
|
+
labelText: 'Email',
|
|
397
|
+
prefixIcon: Icon(Icons.email_outlined),
|
|
398
|
+
border: OutlineInputBorder(),
|
|
399
|
+
),
|
|
400
|
+
validator: (value) {
|
|
401
|
+
if (value == null || value.isEmpty) {
|
|
402
|
+
return 'Please enter your email';
|
|
403
|
+
}
|
|
404
|
+
if (!value.contains('@') || !value.contains('.')) {
|
|
405
|
+
return 'Please enter a valid email';
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
},
|
|
409
|
+
),
|
|
410
|
+
const SizedBox(height: 16),
|
|
411
|
+
|
|
412
|
+
// Password field
|
|
413
|
+
TextFormField(
|
|
414
|
+
controller: _passwordController,
|
|
415
|
+
obscureText: _obscurePassword,
|
|
416
|
+
textInputAction: TextInputAction.done,
|
|
417
|
+
onFieldSubmitted: (_) => _handleLogin(),
|
|
418
|
+
decoration: InputDecoration(
|
|
419
|
+
labelText: 'Password',
|
|
420
|
+
prefixIcon: const Icon(Icons.lock_outlined),
|
|
421
|
+
suffixIcon: IconButton(
|
|
422
|
+
icon: Icon(
|
|
423
|
+
_obscurePassword
|
|
424
|
+
? Icons.visibility_outlined
|
|
425
|
+
: Icons.visibility_off_outlined,
|
|
426
|
+
),
|
|
427
|
+
onPressed: () {
|
|
428
|
+
setState(() => _obscurePassword = !_obscurePassword);
|
|
429
|
+
},
|
|
430
|
+
),
|
|
431
|
+
border: const OutlineInputBorder(),
|
|
432
|
+
),
|
|
433
|
+
validator: (value) {
|
|
434
|
+
if (value == null || value.isEmpty) {
|
|
435
|
+
return 'Please enter your password';
|
|
436
|
+
}
|
|
437
|
+
if (value.length < 6) {
|
|
438
|
+
return 'Password must be at least 6 characters';
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
},
|
|
442
|
+
),
|
|
443
|
+
const SizedBox(height: 8),
|
|
444
|
+
|
|
445
|
+
// Forgot password
|
|
446
|
+
Align(
|
|
447
|
+
alignment: Alignment.centerRight,
|
|
448
|
+
child: TextButton(
|
|
449
|
+
onPressed: () => context.push('/forgot-password'),
|
|
450
|
+
child: const Text('Forgot Password?'),
|
|
451
|
+
),
|
|
452
|
+
),
|
|
453
|
+
const SizedBox(height: 24),
|
|
454
|
+
|
|
455
|
+
// Login button
|
|
456
|
+
FilledButton(
|
|
457
|
+
onPressed: authState.isLoading ? null : _handleLogin,
|
|
458
|
+
style: FilledButton.styleFrom(
|
|
459
|
+
minimumSize: const Size.fromHeight(56),
|
|
460
|
+
),
|
|
461
|
+
child: authState.isLoading
|
|
462
|
+
? const SizedBox(
|
|
463
|
+
height: 24,
|
|
464
|
+
width: 24,
|
|
465
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
466
|
+
)
|
|
467
|
+
: const Text('Sign In'),
|
|
468
|
+
),
|
|
469
|
+
const SizedBox(height: 24),
|
|
470
|
+
|
|
471
|
+
// Register link
|
|
472
|
+
Row(
|
|
473
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
474
|
+
children: [
|
|
475
|
+
Text(
|
|
476
|
+
"Don't have an account?",
|
|
477
|
+
style: theme.textTheme.bodyMedium,
|
|
478
|
+
),
|
|
479
|
+
TextButton(
|
|
480
|
+
onPressed: () => context.push('/register'),
|
|
481
|
+
child: const Text('Sign Up'),
|
|
482
|
+
),
|
|
483
|
+
],
|
|
484
|
+
),
|
|
485
|
+
],
|
|
486
|
+
),
|
|
487
|
+
),
|
|
488
|
+
),
|
|
489
|
+
),
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Network Layer
|
|
496
|
+
|
|
497
|
+
### Dio Client with Interceptors
|
|
498
|
+
|
|
499
|
+
```dart
|
|
500
|
+
// lib/core/network/api_client.dart
|
|
501
|
+
class ApiClient {
|
|
502
|
+
late final Dio _dio;
|
|
503
|
+
final SecureStorage _storage;
|
|
504
|
+
|
|
505
|
+
ApiClient({
|
|
506
|
+
required String baseUrl,
|
|
507
|
+
required SecureStorage storage,
|
|
508
|
+
}) : _storage = storage {
|
|
509
|
+
_dio = Dio(BaseOptions(
|
|
510
|
+
baseUrl: baseUrl,
|
|
511
|
+
connectTimeout: const Duration(seconds: 30),
|
|
512
|
+
receiveTimeout: const Duration(seconds: 30),
|
|
513
|
+
headers: {
|
|
514
|
+
'Content-Type': 'application/json',
|
|
515
|
+
'Accept': 'application/json',
|
|
516
|
+
},
|
|
517
|
+
));
|
|
518
|
+
|
|
519
|
+
_dio.interceptors.addAll([
|
|
520
|
+
AuthInterceptor(_storage, _dio),
|
|
521
|
+
LogInterceptor(
|
|
522
|
+
requestBody: true,
|
|
523
|
+
responseBody: true,
|
|
524
|
+
logPrint: (log) => debugPrint(log.toString()),
|
|
525
|
+
),
|
|
526
|
+
RetryInterceptor(_dio),
|
|
527
|
+
]);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
Future<T> get<T>(
|
|
531
|
+
String path, {
|
|
532
|
+
Map<String, dynamic>? queryParameters,
|
|
533
|
+
required T Function(dynamic) fromJson,
|
|
534
|
+
}) async {
|
|
535
|
+
final response = await _dio.get(path, queryParameters: queryParameters);
|
|
536
|
+
return fromJson(response.data);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
Future<T> post<T>(
|
|
540
|
+
String path, {
|
|
541
|
+
dynamic data,
|
|
542
|
+
required T Function(dynamic) fromJson,
|
|
543
|
+
}) async {
|
|
544
|
+
final response = await _dio.post(path, data: data);
|
|
545
|
+
return fromJson(response.data);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
Future<T> put<T>(
|
|
549
|
+
String path, {
|
|
550
|
+
dynamic data,
|
|
551
|
+
required T Function(dynamic) fromJson,
|
|
552
|
+
}) async {
|
|
553
|
+
final response = await _dio.put(path, data: data);
|
|
554
|
+
return fromJson(response.data);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
Future<void> delete(String path) async {
|
|
558
|
+
await _dio.delete(path);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// lib/core/network/interceptors/auth_interceptor.dart
|
|
563
|
+
class AuthInterceptor extends Interceptor {
|
|
564
|
+
final SecureStorage _storage;
|
|
565
|
+
final Dio _dio;
|
|
566
|
+
bool _isRefreshing = false;
|
|
567
|
+
final _pendingRequests = <({RequestOptions options, ErrorInterceptorHandler handler})>[];
|
|
568
|
+
|
|
569
|
+
AuthInterceptor(this._storage, this._dio);
|
|
570
|
+
|
|
571
|
+
@override
|
|
572
|
+
Future<void> onRequest(
|
|
573
|
+
RequestOptions options,
|
|
574
|
+
RequestInterceptorHandler handler,
|
|
575
|
+
) async {
|
|
576
|
+
final tokens = await _storage.getTokens();
|
|
577
|
+
if (tokens != null) {
|
|
578
|
+
options.headers['Authorization'] = 'Bearer ${tokens.accessToken}';
|
|
579
|
+
}
|
|
580
|
+
handler.next(options);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@override
|
|
584
|
+
Future<void> onError(
|
|
585
|
+
DioException err,
|
|
586
|
+
ErrorInterceptorHandler handler,
|
|
587
|
+
) async {
|
|
588
|
+
if (err.response?.statusCode != 401) {
|
|
589
|
+
return handler.next(err);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Queue the request if already refreshing
|
|
593
|
+
if (_isRefreshing) {
|
|
594
|
+
_pendingRequests.add((options: err.requestOptions, handler: handler));
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
_isRefreshing = true;
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
final tokens = await _storage.getTokens();
|
|
602
|
+
if (tokens == null) {
|
|
603
|
+
return handler.next(err);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Refresh token
|
|
607
|
+
final response = await _dio.post(
|
|
608
|
+
'/auth/refresh',
|
|
609
|
+
data: {'refresh_token': tokens.refreshToken},
|
|
610
|
+
options: Options(headers: {'Authorization': null}),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
final newTokens = AuthTokensModel.fromJson(response.data);
|
|
614
|
+
await _storage.saveTokens(newTokens);
|
|
615
|
+
|
|
616
|
+
// Retry original request
|
|
617
|
+
final retryResponse = await _retryRequest(err.requestOptions, newTokens);
|
|
618
|
+
handler.resolve(retryResponse);
|
|
619
|
+
|
|
620
|
+
// Retry pending requests
|
|
621
|
+
for (final pending in _pendingRequests) {
|
|
622
|
+
final response = await _retryRequest(pending.options, newTokens);
|
|
623
|
+
pending.handler.resolve(response);
|
|
624
|
+
}
|
|
625
|
+
} on DioException catch (e) {
|
|
626
|
+
// Refresh failed - clear tokens and reject all pending
|
|
627
|
+
await _storage.clearTokens();
|
|
628
|
+
handler.next(e);
|
|
629
|
+
|
|
630
|
+
for (final pending in _pendingRequests) {
|
|
631
|
+
pending.handler.next(e);
|
|
632
|
+
}
|
|
633
|
+
} finally {
|
|
634
|
+
_isRefreshing = false;
|
|
635
|
+
_pendingRequests.clear();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
Future<Response> _retryRequest(
|
|
640
|
+
RequestOptions options,
|
|
641
|
+
AuthTokensModel tokens,
|
|
642
|
+
) async {
|
|
643
|
+
options.headers['Authorization'] = 'Bearer ${tokens.accessToken}';
|
|
644
|
+
return _dio.fetch(options);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// lib/core/network/interceptors/retry_interceptor.dart
|
|
649
|
+
class RetryInterceptor extends Interceptor {
|
|
650
|
+
final Dio _dio;
|
|
651
|
+
final int maxRetries;
|
|
652
|
+
final Duration retryDelay;
|
|
653
|
+
|
|
654
|
+
RetryInterceptor(
|
|
655
|
+
this._dio, {
|
|
656
|
+
this.maxRetries = 3,
|
|
657
|
+
this.retryDelay = const Duration(seconds: 1),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
@override
|
|
661
|
+
Future<void> onError(
|
|
662
|
+
DioException err,
|
|
663
|
+
ErrorInterceptorHandler handler,
|
|
664
|
+
) async {
|
|
665
|
+
final shouldRetry = _shouldRetry(err);
|
|
666
|
+
final retryCount = err.requestOptions.extra['retryCount'] ?? 0;
|
|
667
|
+
|
|
668
|
+
if (!shouldRetry || retryCount >= maxRetries) {
|
|
669
|
+
return handler.next(err);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
await Future.delayed(retryDelay * (retryCount + 1));
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
err.requestOptions.extra['retryCount'] = retryCount + 1;
|
|
676
|
+
final response = await _dio.fetch(err.requestOptions);
|
|
677
|
+
handler.resolve(response);
|
|
678
|
+
} on DioException catch (e) {
|
|
679
|
+
handler.next(e);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
bool _shouldRetry(DioException err) {
|
|
684
|
+
return err.type == DioExceptionType.connectionTimeout ||
|
|
685
|
+
err.type == DioExceptionType.sendTimeout ||
|
|
686
|
+
err.type == DioExceptionType.receiveTimeout ||
|
|
687
|
+
(err.response?.statusCode != null &&
|
|
688
|
+
err.response!.statusCode! >= 500);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
## Platform Channels
|
|
694
|
+
|
|
695
|
+
### Complete Native Bridge
|
|
696
|
+
|
|
697
|
+
```dart
|
|
698
|
+
// lib/core/platform/native_bridge.dart
|
|
699
|
+
class NativeBridge {
|
|
700
|
+
static const _methodChannel = MethodChannel('com.example.app/native');
|
|
701
|
+
static const _eventChannel = EventChannel('com.example.app/events');
|
|
702
|
+
|
|
703
|
+
// Singleton pattern
|
|
704
|
+
static final NativeBridge _instance = NativeBridge._();
|
|
705
|
+
static NativeBridge get instance => _instance;
|
|
706
|
+
NativeBridge._() {
|
|
707
|
+
_setupMethodCallHandler();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Method calls
|
|
711
|
+
Future<String> getPlatformVersion() async {
|
|
712
|
+
try {
|
|
713
|
+
final version = await _methodChannel.invokeMethod<String>('getPlatformVersion');
|
|
714
|
+
return version ?? 'Unknown';
|
|
715
|
+
} on PlatformException catch (e) {
|
|
716
|
+
throw NativeBridgeException('Failed to get platform version: ${e.message}');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
Future<DeviceInfo> getDeviceInfo() async {
|
|
721
|
+
try {
|
|
722
|
+
final result = await _methodChannel.invokeMapMethod<String, dynamic>('getDeviceInfo');
|
|
723
|
+
return DeviceInfo.fromMap(result ?? {});
|
|
724
|
+
} on PlatformException catch (e) {
|
|
725
|
+
throw NativeBridgeException('Failed to get device info: ${e.message}');
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
Future<void> shareContent({
|
|
730
|
+
required String text,
|
|
731
|
+
String? title,
|
|
732
|
+
String? url,
|
|
733
|
+
}) async {
|
|
734
|
+
try {
|
|
735
|
+
await _methodChannel.invokeMethod('share', {
|
|
736
|
+
'text': text,
|
|
737
|
+
if (title != null) 'title': title,
|
|
738
|
+
if (url != null) 'url': url,
|
|
739
|
+
});
|
|
740
|
+
} on PlatformException catch (e) {
|
|
741
|
+
throw NativeBridgeException('Failed to share: ${e.message}');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
Future<bool> requestPermission(PermissionType type) async {
|
|
746
|
+
try {
|
|
747
|
+
final result = await _methodChannel.invokeMethod<bool>(
|
|
748
|
+
'requestPermission',
|
|
749
|
+
{'type': type.name},
|
|
750
|
+
);
|
|
751
|
+
return result ?? false;
|
|
752
|
+
} on PlatformException catch (e) {
|
|
753
|
+
throw NativeBridgeException('Failed to request permission: ${e.message}');
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Event streams
|
|
758
|
+
Stream<BatteryState> watchBatteryState() {
|
|
759
|
+
return _eventChannel.receiveBroadcastStream('battery').map((event) {
|
|
760
|
+
final data = event as Map<dynamic, dynamic>;
|
|
761
|
+
return BatteryState(
|
|
762
|
+
level: data['level'] as int,
|
|
763
|
+
isCharging: data['isCharging'] as bool,
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
Stream<ConnectivityState> watchConnectivity() {
|
|
769
|
+
return _eventChannel.receiveBroadcastStream('connectivity').map((event) {
|
|
770
|
+
final data = event as Map<dynamic, dynamic>;
|
|
771
|
+
return ConnectivityState(
|
|
772
|
+
isConnected: data['isConnected'] as bool,
|
|
773
|
+
type: ConnectivityType.values.byName(data['type'] as String),
|
|
774
|
+
);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Bidirectional communication
|
|
779
|
+
void _setupMethodCallHandler() {
|
|
780
|
+
_methodChannel.setMethodCallHandler((call) async {
|
|
781
|
+
switch (call.method) {
|
|
782
|
+
case 'onDeepLink':
|
|
783
|
+
final url = call.arguments as String;
|
|
784
|
+
_handleDeepLink(url);
|
|
785
|
+
return true;
|
|
786
|
+
case 'onPushNotification':
|
|
787
|
+
final data = call.arguments as Map<dynamic, dynamic>;
|
|
788
|
+
_handlePushNotification(data.cast<String, dynamic>());
|
|
789
|
+
return true;
|
|
790
|
+
default:
|
|
791
|
+
throw MissingPluginException('Method not implemented: ${call.method}');
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
void _handleDeepLink(String url) {
|
|
797
|
+
// Handle deep link
|
|
798
|
+
debugPrint('Received deep link: $url');
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
void _handlePushNotification(Map<String, dynamic> data) {
|
|
802
|
+
// Handle push notification
|
|
803
|
+
debugPrint('Received notification: $data');
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Models
|
|
808
|
+
class DeviceInfo {
|
|
809
|
+
final String platform;
|
|
810
|
+
final String version;
|
|
811
|
+
final String model;
|
|
812
|
+
final String manufacturer;
|
|
813
|
+
|
|
814
|
+
const DeviceInfo({
|
|
815
|
+
required this.platform,
|
|
816
|
+
required this.version,
|
|
817
|
+
required this.model,
|
|
818
|
+
required this.manufacturer,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
factory DeviceInfo.fromMap(Map<String, dynamic> map) => DeviceInfo(
|
|
822
|
+
platform: map['platform'] as String? ?? 'unknown',
|
|
823
|
+
version: map['version'] as String? ?? 'unknown',
|
|
824
|
+
model: map['model'] as String? ?? 'unknown',
|
|
825
|
+
manufacturer: map['manufacturer'] as String? ?? 'unknown',
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
class BatteryState {
|
|
830
|
+
final int level;
|
|
831
|
+
final bool isCharging;
|
|
832
|
+
const BatteryState({required this.level, required this.isCharging});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
class ConnectivityState {
|
|
836
|
+
final bool isConnected;
|
|
837
|
+
final ConnectivityType type;
|
|
838
|
+
const ConnectivityState({required this.isConnected, required this.type});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
enum ConnectivityType { none, wifi, mobile, ethernet }
|
|
842
|
+
enum PermissionType { camera, microphone, location, storage, notifications }
|
|
843
|
+
|
|
844
|
+
class NativeBridgeException implements Exception {
|
|
845
|
+
final String message;
|
|
846
|
+
const NativeBridgeException(this.message);
|
|
847
|
+
|
|
848
|
+
@override
|
|
849
|
+
String toString() => 'NativeBridgeException: $message';
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
## Testing Examples
|
|
854
|
+
|
|
855
|
+
### Widget Tests with Riverpod
|
|
856
|
+
|
|
857
|
+
```dart
|
|
858
|
+
// test/features/auth/presentation/screens/login_screen_test.dart
|
|
859
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
860
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
861
|
+
import 'package:mocktail/mocktail.dart';
|
|
862
|
+
|
|
863
|
+
class MockAuthRepository extends Mock implements AuthRepository {}
|
|
864
|
+
|
|
865
|
+
void main() {
|
|
866
|
+
late ProviderContainer container;
|
|
867
|
+
late MockAuthRepository mockAuthRepository;
|
|
868
|
+
|
|
869
|
+
setUp(() {
|
|
870
|
+
mockAuthRepository = MockAuthRepository();
|
|
871
|
+
|
|
872
|
+
// Default mock behavior
|
|
873
|
+
when(() => mockAuthRepository.restoreSession())
|
|
874
|
+
.thenAnswer((_) async => null);
|
|
875
|
+
when(() => mockAuthRepository.watchAuthState())
|
|
876
|
+
.thenAnswer((_) => const Stream.empty());
|
|
877
|
+
|
|
878
|
+
container = ProviderContainer(overrides: [
|
|
879
|
+
authRepositoryProvider.overrideWithValue(mockAuthRepository),
|
|
880
|
+
]);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
tearDown(() => container.dispose());
|
|
884
|
+
|
|
885
|
+
Widget buildTestWidget() {
|
|
886
|
+
return UncontrolledProviderScope(
|
|
887
|
+
container: container,
|
|
888
|
+
child: MaterialApp(
|
|
889
|
+
home: const LoginScreen(),
|
|
890
|
+
),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
group('LoginScreen', () {
|
|
895
|
+
testWidgets('renders email and password fields', (tester) async {
|
|
896
|
+
await tester.pumpWidget(buildTestWidget());
|
|
897
|
+
|
|
898
|
+
expect(find.byType(TextFormField), findsNWidgets(2));
|
|
899
|
+
expect(find.text('Email'), findsOneWidget);
|
|
900
|
+
expect(find.text('Password'), findsOneWidget);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
testWidgets('shows validation errors for empty fields', (tester) async {
|
|
904
|
+
await tester.pumpWidget(buildTestWidget());
|
|
905
|
+
|
|
906
|
+
await tester.tap(find.byType(FilledButton));
|
|
907
|
+
await tester.pump();
|
|
908
|
+
|
|
909
|
+
expect(find.text('Please enter your email'), findsOneWidget);
|
|
910
|
+
expect(find.text('Please enter your password'), findsOneWidget);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
testWidgets('shows validation error for invalid email', (tester) async {
|
|
914
|
+
await tester.pumpWidget(buildTestWidget());
|
|
915
|
+
|
|
916
|
+
await tester.enterText(
|
|
917
|
+
find.widgetWithText(TextFormField, 'Email'),
|
|
918
|
+
'invalid-email',
|
|
919
|
+
);
|
|
920
|
+
await tester.enterText(
|
|
921
|
+
find.widgetWithText(TextFormField, 'Password'),
|
|
922
|
+
'password123',
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
await tester.tap(find.byType(FilledButton));
|
|
926
|
+
await tester.pump();
|
|
927
|
+
|
|
928
|
+
expect(find.text('Please enter a valid email'), findsOneWidget);
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
testWidgets('calls login when form is valid', (tester) async {
|
|
932
|
+
when(() => mockAuthRepository.login(any(), any()))
|
|
933
|
+
.thenAnswer((_) async => const User(
|
|
934
|
+
id: '1',
|
|
935
|
+
email: 'test@example.com',
|
|
936
|
+
name: 'Test User',
|
|
937
|
+
));
|
|
938
|
+
|
|
939
|
+
await tester.pumpWidget(buildTestWidget());
|
|
940
|
+
|
|
941
|
+
await tester.enterText(
|
|
942
|
+
find.widgetWithText(TextFormField, 'Email'),
|
|
943
|
+
'test@example.com',
|
|
944
|
+
);
|
|
945
|
+
await tester.enterText(
|
|
946
|
+
find.widgetWithText(TextFormField, 'Password'),
|
|
947
|
+
'password123',
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
await tester.tap(find.byType(FilledButton));
|
|
951
|
+
await tester.pump();
|
|
952
|
+
|
|
953
|
+
verify(() => mockAuthRepository.login('test@example.com', 'password123'))
|
|
954
|
+
.called(1);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
testWidgets('shows loading indicator during login', (tester) async {
|
|
958
|
+
when(() => mockAuthRepository.login(any(), any()))
|
|
959
|
+
.thenAnswer((_) async {
|
|
960
|
+
await Future.delayed(const Duration(seconds: 2));
|
|
961
|
+
return const User(
|
|
962
|
+
id: '1',
|
|
963
|
+
email: 'test@example.com',
|
|
964
|
+
name: 'Test User',
|
|
965
|
+
);
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
await tester.pumpWidget(buildTestWidget());
|
|
969
|
+
|
|
970
|
+
await tester.enterText(
|
|
971
|
+
find.widgetWithText(TextFormField, 'Email'),
|
|
972
|
+
'test@example.com',
|
|
973
|
+
);
|
|
974
|
+
await tester.enterText(
|
|
975
|
+
find.widgetWithText(TextFormField, 'Password'),
|
|
976
|
+
'password123',
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
await tester.tap(find.byType(FilledButton));
|
|
980
|
+
await tester.pump();
|
|
981
|
+
|
|
982
|
+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
testWidgets('shows error snackbar on login failure', (tester) async {
|
|
986
|
+
when(() => mockAuthRepository.login(any(), any()))
|
|
987
|
+
.thenThrow(const InvalidCredentialsError());
|
|
988
|
+
|
|
989
|
+
await tester.pumpWidget(buildTestWidget());
|
|
990
|
+
|
|
991
|
+
await tester.enterText(
|
|
992
|
+
find.widgetWithText(TextFormField, 'Email'),
|
|
993
|
+
'test@example.com',
|
|
994
|
+
);
|
|
995
|
+
await tester.enterText(
|
|
996
|
+
find.widgetWithText(TextFormField, 'Password'),
|
|
997
|
+
'wrongpassword',
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
await tester.tap(find.byType(FilledButton));
|
|
1001
|
+
await tester.pumpAndSettle();
|
|
1002
|
+
|
|
1003
|
+
expect(find.byType(SnackBar), findsOneWidget);
|
|
1004
|
+
expect(find.text('Invalid email or password'), findsOneWidget);
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### Integration Tests
|
|
1011
|
+
|
|
1012
|
+
```dart
|
|
1013
|
+
// integration_test/auth_flow_test.dart
|
|
1014
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
1015
|
+
import 'package:integration_test/integration_test.dart';
|
|
1016
|
+
|
|
1017
|
+
void main() {
|
|
1018
|
+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
1019
|
+
|
|
1020
|
+
group('Authentication Flow', () {
|
|
1021
|
+
testWidgets('complete login and logout flow', (tester) async {
|
|
1022
|
+
await tester.pumpWidget(const MyApp());
|
|
1023
|
+
await tester.pumpAndSettle();
|
|
1024
|
+
|
|
1025
|
+
// Verify we're on login screen
|
|
1026
|
+
expect(find.text('Welcome Back'), findsOneWidget);
|
|
1027
|
+
|
|
1028
|
+
// Enter credentials
|
|
1029
|
+
await tester.enterText(
|
|
1030
|
+
find.byKey(const Key('email_field')),
|
|
1031
|
+
'test@example.com',
|
|
1032
|
+
);
|
|
1033
|
+
await tester.enterText(
|
|
1034
|
+
find.byKey(const Key('password_field')),
|
|
1035
|
+
'password123',
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
// Tap login
|
|
1039
|
+
await tester.tap(find.byKey(const Key('login_button')));
|
|
1040
|
+
await tester.pumpAndSettle();
|
|
1041
|
+
|
|
1042
|
+
// Verify navigation to home
|
|
1043
|
+
expect(find.byType(HomeScreen), findsOneWidget);
|
|
1044
|
+
expect(find.text('Test User'), findsOneWidget);
|
|
1045
|
+
|
|
1046
|
+
// Navigate to profile
|
|
1047
|
+
await tester.tap(find.byIcon(Icons.person));
|
|
1048
|
+
await tester.pumpAndSettle();
|
|
1049
|
+
|
|
1050
|
+
// Tap logout
|
|
1051
|
+
await tester.tap(find.text('Logout'));
|
|
1052
|
+
await tester.pumpAndSettle();
|
|
1053
|
+
|
|
1054
|
+
// Verify back on login screen
|
|
1055
|
+
expect(find.text('Welcome Back'), findsOneWidget);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
testWidgets('session restoration on app restart', (tester) async {
|
|
1059
|
+
// First launch - login
|
|
1060
|
+
await tester.pumpWidget(const MyApp());
|
|
1061
|
+
await tester.pumpAndSettle();
|
|
1062
|
+
|
|
1063
|
+
await tester.enterText(
|
|
1064
|
+
find.byKey(const Key('email_field')),
|
|
1065
|
+
'test@example.com',
|
|
1066
|
+
);
|
|
1067
|
+
await tester.enterText(
|
|
1068
|
+
find.byKey(const Key('password_field')),
|
|
1069
|
+
'password123',
|
|
1070
|
+
);
|
|
1071
|
+
await tester.tap(find.byKey(const Key('login_button')));
|
|
1072
|
+
await tester.pumpAndSettle();
|
|
1073
|
+
|
|
1074
|
+
expect(find.byType(HomeScreen), findsOneWidget);
|
|
1075
|
+
|
|
1076
|
+
// Simulate app restart
|
|
1077
|
+
await tester.pumpWidget(const MyApp());
|
|
1078
|
+
await tester.pumpAndSettle();
|
|
1079
|
+
|
|
1080
|
+
// Should restore session and show home
|
|
1081
|
+
expect(find.byType(HomeScreen), findsOneWidget);
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
---
|
|
1088
|
+
|
|
1089
|
+
Version: 1.0.0
|
|
1090
|
+
Last Updated: 2025-12-07
|