maxsim-flutter 1.0.0

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.
Files changed (281) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +445 -0
  4. package/claude-plugin/.claude-plugin/plugin.json +46 -0
  5. package/claude-plugin/agents/flutter-setup-agent.md +54 -0
  6. package/claude-plugin/commands/flutter-add.md +32 -0
  7. package/claude-plugin/commands/flutter-create.md +28 -0
  8. package/claude-plugin/commands/flutter-migrate.md +28 -0
  9. package/claude-plugin/skills/flutter-scaffolding/SKILL.md +109 -0
  10. package/dist/claude-setup/agent-writer.d.ts +21 -0
  11. package/dist/claude-setup/agent-writer.d.ts.map +1 -0
  12. package/dist/claude-setup/agent-writer.js +365 -0
  13. package/dist/claude-setup/agent-writer.js.map +1 -0
  14. package/dist/claude-setup/claude-md-generator.d.ts +7 -0
  15. package/dist/claude-setup/claude-md-generator.d.ts.map +1 -0
  16. package/dist/claude-setup/claude-md-generator.js +428 -0
  17. package/dist/claude-setup/claude-md-generator.js.map +1 -0
  18. package/dist/claude-setup/commands-writer.d.ts +7 -0
  19. package/dist/claude-setup/commands-writer.d.ts.map +1 -0
  20. package/dist/claude-setup/commands-writer.js +303 -0
  21. package/dist/claude-setup/commands-writer.js.map +1 -0
  22. package/dist/claude-setup/hooks-writer.d.ts +3 -0
  23. package/dist/claude-setup/hooks-writer.d.ts.map +1 -0
  24. package/dist/claude-setup/hooks-writer.js +34 -0
  25. package/dist/claude-setup/hooks-writer.js.map +1 -0
  26. package/dist/claude-setup/index.d.ts +10 -0
  27. package/dist/claude-setup/index.d.ts.map +1 -0
  28. package/dist/claude-setup/index.js +9 -0
  29. package/dist/claude-setup/index.js.map +1 -0
  30. package/dist/claude-setup/mcp-config-writer.d.ts +3 -0
  31. package/dist/claude-setup/mcp-config-writer.d.ts.map +1 -0
  32. package/dist/claude-setup/mcp-config-writer.js +42 -0
  33. package/dist/claude-setup/mcp-config-writer.js.map +1 -0
  34. package/dist/claude-setup/plugin-assembler.d.ts +2 -0
  35. package/dist/claude-setup/plugin-assembler.d.ts.map +1 -0
  36. package/dist/claude-setup/plugin-assembler.js +3 -0
  37. package/dist/claude-setup/plugin-assembler.js.map +1 -0
  38. package/dist/claude-setup/prd-generator.d.ts +10 -0
  39. package/dist/claude-setup/prd-generator.d.ts.map +1 -0
  40. package/dist/claude-setup/prd-generator.js +295 -0
  41. package/dist/claude-setup/prd-generator.js.map +1 -0
  42. package/dist/claude-setup/setup-orchestrator.d.ts +11 -0
  43. package/dist/claude-setup/setup-orchestrator.d.ts.map +1 -0
  44. package/dist/claude-setup/setup-orchestrator.js +46 -0
  45. package/dist/claude-setup/setup-orchestrator.js.map +1 -0
  46. package/dist/claude-setup/skill-writer.d.ts +3 -0
  47. package/dist/claude-setup/skill-writer.d.ts.map +1 -0
  48. package/dist/claude-setup/skill-writer.js +256 -0
  49. package/dist/claude-setup/skill-writer.js.map +1 -0
  50. package/dist/cli/commands/add.d.ts +16 -0
  51. package/dist/cli/commands/add.d.ts.map +1 -0
  52. package/dist/cli/commands/add.js +414 -0
  53. package/dist/cli/commands/add.js.map +1 -0
  54. package/dist/cli/commands/create.d.ts +3 -0
  55. package/dist/cli/commands/create.d.ts.map +1 -0
  56. package/dist/cli/commands/create.js +161 -0
  57. package/dist/cli/commands/create.js.map +1 -0
  58. package/dist/cli/commands/list.d.ts +12 -0
  59. package/dist/cli/commands/list.d.ts.map +1 -0
  60. package/dist/cli/commands/list.js +148 -0
  61. package/dist/cli/commands/list.js.map +1 -0
  62. package/dist/cli/commands/migrate.d.ts +3 -0
  63. package/dist/cli/commands/migrate.d.ts.map +1 -0
  64. package/dist/cli/commands/migrate.js +332 -0
  65. package/dist/cli/commands/migrate.js.map +1 -0
  66. package/dist/cli/index.d.ts +3 -0
  67. package/dist/cli/index.d.ts.map +1 -0
  68. package/dist/cli/index.js +34 -0
  69. package/dist/cli/index.js.map +1 -0
  70. package/dist/cli/ui/prompts.d.ts +9 -0
  71. package/dist/cli/ui/prompts.d.ts.map +1 -0
  72. package/dist/cli/ui/prompts.js +74 -0
  73. package/dist/cli/ui/prompts.js.map +1 -0
  74. package/dist/cli/ui/spinner.d.ts +2 -0
  75. package/dist/cli/ui/spinner.d.ts.map +1 -0
  76. package/dist/cli/ui/spinner.js +5 -0
  77. package/dist/cli/ui/spinner.js.map +1 -0
  78. package/dist/cli/version-check.d.ts +7 -0
  79. package/dist/cli/version-check.d.ts.map +1 -0
  80. package/dist/cli/version-check.js +92 -0
  81. package/dist/cli/version-check.js.map +1 -0
  82. package/dist/core/config/defaults.d.ts +3 -0
  83. package/dist/core/config/defaults.d.ts.map +1 -0
  84. package/dist/core/config/defaults.js +21 -0
  85. package/dist/core/config/defaults.js.map +1 -0
  86. package/dist/core/config/loader.d.ts +4 -0
  87. package/dist/core/config/loader.d.ts.map +1 -0
  88. package/dist/core/config/loader.js +38 -0
  89. package/dist/core/config/loader.js.map +1 -0
  90. package/dist/core/config/schema.d.ts +406 -0
  91. package/dist/core/config/schema.d.ts.map +1 -0
  92. package/dist/core/config/schema.js +119 -0
  93. package/dist/core/config/schema.js.map +1 -0
  94. package/dist/core/context.d.ts +57 -0
  95. package/dist/core/context.d.ts.map +1 -0
  96. package/dist/core/context.js +99 -0
  97. package/dist/core/context.js.map +1 -0
  98. package/dist/core/detector.d.ts +28 -0
  99. package/dist/core/detector.d.ts.map +1 -0
  100. package/dist/core/detector.js +200 -0
  101. package/dist/core/detector.js.map +1 -0
  102. package/dist/core/validator.d.ts +7 -0
  103. package/dist/core/validator.d.ts.map +1 -0
  104. package/dist/core/validator.js +35 -0
  105. package/dist/core/validator.js.map +1 -0
  106. package/dist/index.d.ts +21 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +14 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/modules/composer.d.ts +50 -0
  111. package/dist/modules/composer.d.ts.map +1 -0
  112. package/dist/modules/composer.js +122 -0
  113. package/dist/modules/composer.js.map +1 -0
  114. package/dist/modules/definitions/analytics/module.d.ts +7 -0
  115. package/dist/modules/definitions/analytics/module.d.ts.map +1 -0
  116. package/dist/modules/definitions/analytics/module.js +28 -0
  117. package/dist/modules/definitions/analytics/module.js.map +1 -0
  118. package/dist/modules/definitions/api/module.d.ts +7 -0
  119. package/dist/modules/definitions/api/module.d.ts.map +1 -0
  120. package/dist/modules/definitions/api/module.js +41 -0
  121. package/dist/modules/definitions/api/module.js.map +1 -0
  122. package/dist/modules/definitions/auth/module.d.ts +7 -0
  123. package/dist/modules/definitions/auth/module.d.ts.map +1 -0
  124. package/dist/modules/definitions/auth/module.js +53 -0
  125. package/dist/modules/definitions/auth/module.js.map +1 -0
  126. package/dist/modules/definitions/cicd/module.d.ts +7 -0
  127. package/dist/modules/definitions/cicd/module.d.ts.map +1 -0
  128. package/dist/modules/definitions/cicd/module.js +34 -0
  129. package/dist/modules/definitions/cicd/module.js.map +1 -0
  130. package/dist/modules/definitions/core/module.d.ts +7 -0
  131. package/dist/modules/definitions/core/module.d.ts.map +1 -0
  132. package/dist/modules/definitions/core/module.js +31 -0
  133. package/dist/modules/definitions/core/module.js.map +1 -0
  134. package/dist/modules/definitions/database/module.d.ts +8 -0
  135. package/dist/modules/definitions/database/module.d.ts.map +1 -0
  136. package/dist/modules/definitions/database/module.js +47 -0
  137. package/dist/modules/definitions/database/module.js.map +1 -0
  138. package/dist/modules/definitions/deep-linking/module.d.ts +7 -0
  139. package/dist/modules/definitions/deep-linking/module.d.ts.map +1 -0
  140. package/dist/modules/definitions/deep-linking/module.js +48 -0
  141. package/dist/modules/definitions/deep-linking/module.js.map +1 -0
  142. package/dist/modules/definitions/i18n/module.d.ts +7 -0
  143. package/dist/modules/definitions/i18n/module.d.ts.map +1 -0
  144. package/dist/modules/definitions/i18n/module.js +37 -0
  145. package/dist/modules/definitions/i18n/module.js.map +1 -0
  146. package/dist/modules/definitions/push/module.d.ts +7 -0
  147. package/dist/modules/definitions/push/module.d.ts.map +1 -0
  148. package/dist/modules/definitions/push/module.js +40 -0
  149. package/dist/modules/definitions/push/module.js.map +1 -0
  150. package/dist/modules/definitions/theme/module.d.ts +7 -0
  151. package/dist/modules/definitions/theme/module.d.ts.map +1 -0
  152. package/dist/modules/definitions/theme/module.js +42 -0
  153. package/dist/modules/definitions/theme/module.js.map +1 -0
  154. package/dist/modules/registry.d.ts +50 -0
  155. package/dist/modules/registry.d.ts.map +1 -0
  156. package/dist/modules/registry.js +104 -0
  157. package/dist/modules/registry.js.map +1 -0
  158. package/dist/modules/resolver.d.ts +42 -0
  159. package/dist/modules/resolver.d.ts.map +1 -0
  160. package/dist/modules/resolver.js +140 -0
  161. package/dist/modules/resolver.js.map +1 -0
  162. package/dist/ralph/prd-generator.d.ts +2 -0
  163. package/dist/ralph/prd-generator.d.ts.map +1 -0
  164. package/dist/ralph/prd-generator.js +3 -0
  165. package/dist/ralph/prd-generator.js.map +1 -0
  166. package/dist/ralph/ralph-converter.d.ts +2 -0
  167. package/dist/ralph/ralph-converter.d.ts.map +1 -0
  168. package/dist/ralph/ralph-converter.js +3 -0
  169. package/dist/ralph/ralph-converter.js.map +1 -0
  170. package/dist/ralph/story-sizer.d.ts +2 -0
  171. package/dist/ralph/story-sizer.d.ts.map +1 -0
  172. package/dist/ralph/story-sizer.js +3 -0
  173. package/dist/ralph/story-sizer.js.map +1 -0
  174. package/dist/scaffold/engine.d.ts +40 -0
  175. package/dist/scaffold/engine.d.ts.map +1 -0
  176. package/dist/scaffold/engine.js +233 -0
  177. package/dist/scaffold/engine.js.map +1 -0
  178. package/dist/scaffold/file-writer.d.ts +22 -0
  179. package/dist/scaffold/file-writer.d.ts.map +1 -0
  180. package/dist/scaffold/file-writer.js +68 -0
  181. package/dist/scaffold/file-writer.js.map +1 -0
  182. package/dist/scaffold/post-processors/build-runner.d.ts +2 -0
  183. package/dist/scaffold/post-processors/build-runner.d.ts.map +1 -0
  184. package/dist/scaffold/post-processors/build-runner.js +5 -0
  185. package/dist/scaffold/post-processors/build-runner.js.map +1 -0
  186. package/dist/scaffold/post-processors/dart-format.d.ts +2 -0
  187. package/dist/scaffold/post-processors/dart-format.d.ts.map +1 -0
  188. package/dist/scaffold/post-processors/dart-format.js +5 -0
  189. package/dist/scaffold/post-processors/dart-format.js.map +1 -0
  190. package/dist/scaffold/post-processors/flutter-pub-get.d.ts +2 -0
  191. package/dist/scaffold/post-processors/flutter-pub-get.d.ts.map +1 -0
  192. package/dist/scaffold/post-processors/flutter-pub-get.js +5 -0
  193. package/dist/scaffold/post-processors/flutter-pub-get.js.map +1 -0
  194. package/dist/scaffold/renderer.d.ts +20 -0
  195. package/dist/scaffold/renderer.d.ts.map +1 -0
  196. package/dist/scaffold/renderer.js +75 -0
  197. package/dist/scaffold/renderer.js.map +1 -0
  198. package/dist/scaffold/template-helpers.d.ts +25 -0
  199. package/dist/scaffold/template-helpers.d.ts.map +1 -0
  200. package/dist/scaffold/template-helpers.js +115 -0
  201. package/dist/scaffold/template-helpers.js.map +1 -0
  202. package/dist/types/config.d.ts +4 -0
  203. package/dist/types/config.d.ts.map +1 -0
  204. package/dist/types/config.js +2 -0
  205. package/dist/types/config.js.map +1 -0
  206. package/dist/types/module.d.ts +85 -0
  207. package/dist/types/module.d.ts.map +1 -0
  208. package/dist/types/module.js +2 -0
  209. package/dist/types/module.js.map +1 -0
  210. package/dist/types/project.d.ts +13 -0
  211. package/dist/types/project.d.ts.map +1 -0
  212. package/dist/types/project.js +2 -0
  213. package/dist/types/project.js.map +1 -0
  214. package/package.json +80 -0
  215. package/templates/core/analysis_options.yaml.hbs +21 -0
  216. package/templates/core/lib/app.dart.hbs +20 -0
  217. package/templates/core/lib/core/providers/app_providers.dart.hbs +3 -0
  218. package/templates/core/lib/core/router/app_router.dart.hbs +19 -0
  219. package/templates/core/lib/core/theme/app_theme.dart.hbs +15 -0
  220. package/templates/core/lib/features/home/data/models/.gitkeep +0 -0
  221. package/templates/core/lib/features/home/data/repositories/.gitkeep +0 -0
  222. package/templates/core/lib/features/home/domain/entities/.gitkeep +0 -0
  223. package/templates/core/lib/features/home/domain/repositories/.gitkeep +0 -0
  224. package/templates/core/lib/features/home/presentation/pages/home_page.dart.hbs +18 -0
  225. package/templates/core/lib/features/home/presentation/widgets/.gitkeep +0 -0
  226. package/templates/core/lib/main.dart.hbs +11 -0
  227. package/templates/core/pubspec.yaml.hbs +30 -0
  228. package/templates/core/test/widget_test.dart.hbs +16 -0
  229. package/templates/modules/analytics/lib/features/analytics/data/observers/analytics_route_observer.dart.hbs +53 -0
  230. package/templates/modules/analytics/lib/features/analytics/data/services/firebase_analytics_service.dart.hbs +39 -0
  231. package/templates/modules/analytics/lib/features/analytics/domain/services/analytics_service.dart.hbs +25 -0
  232. package/templates/modules/analytics/lib/features/analytics/presentation/providers/analytics_provider.dart.hbs +17 -0
  233. package/templates/modules/analytics/pubspec.partial.yaml +2 -0
  234. package/templates/modules/api/lib/features/api/data/datasources/api_client.dart.hbs +171 -0
  235. package/templates/modules/api/lib/features/api/data/interceptors/auth_interceptor.dart.hbs +25 -0
  236. package/templates/modules/api/lib/features/api/data/interceptors/retry_interceptor.dart.hbs +42 -0
  237. package/templates/modules/api/lib/features/api/data/repositories/api_repository_impl.dart.hbs +21 -0
  238. package/templates/modules/api/lib/features/api/domain/exceptions/api_exception.dart.hbs +31 -0
  239. package/templates/modules/api/lib/features/api/domain/repositories/api_repository.dart.hbs +8 -0
  240. package/templates/modules/api/lib/features/api/presentation/providers/api_provider.dart.hbs +17 -0
  241. package/templates/modules/api/pubspec.partial.yaml +7 -0
  242. package/templates/modules/auth/lib/features/auth/data/datasources/auth_remote_datasource.dart.hbs +181 -0
  243. package/templates/modules/auth/lib/features/auth/data/models/auth_user_model.dart.hbs +27 -0
  244. package/templates/modules/auth/lib/features/auth/data/repositories/auth_repository_impl.dart.hbs +49 -0
  245. package/templates/modules/auth/lib/features/auth/domain/entities/user_entity.dart.hbs +18 -0
  246. package/templates/modules/auth/lib/features/auth/domain/repositories/auth_repository.dart.hbs +22 -0
  247. package/templates/modules/auth/lib/features/auth/domain/usecases/register_usecase.dart.hbs +20 -0
  248. package/templates/modules/auth/lib/features/auth/domain/usecases/sign_in_usecase.dart.hbs +15 -0
  249. package/templates/modules/auth/lib/features/auth/domain/usecases/sign_out_usecase.dart.hbs +11 -0
  250. package/templates/modules/auth/lib/features/auth/presentation/pages/login_page.dart.hbs +118 -0
  251. package/templates/modules/auth/lib/features/auth/presentation/pages/register_page.dart.hbs +131 -0
  252. package/templates/modules/auth/lib/features/auth/presentation/providers/auth_provider.dart.hbs +31 -0
  253. package/templates/modules/auth/pubspec.partial.yaml +13 -0
  254. package/templates/modules/cicd/.github/workflows/ci.yml.hbs +45 -0
  255. package/templates/modules/cicd/.gitlab-ci.yml.hbs +53 -0
  256. package/templates/modules/cicd/bitbucket-pipelines.yml.hbs +39 -0
  257. package/templates/modules/cicd/pubspec.partial.yaml +1 -0
  258. package/templates/modules/database/lib/features/database/data/datasources/database_datasource.dart.hbs +115 -0
  259. package/templates/modules/database/lib/features/database/data/repositories/database_repository_impl.dart.hbs +73 -0
  260. package/templates/modules/database/lib/features/database/domain/repositories/database_repository.dart.hbs +10 -0
  261. package/templates/modules/database/lib/features/database/presentation/providers/database_provider.dart.hbs +43 -0
  262. package/templates/modules/database/pubspec.partial.yaml +27 -0
  263. package/templates/modules/deep-linking/lib/features/deep_linking/data/datasources/deep_link_datasource.dart.hbs +18 -0
  264. package/templates/modules/deep-linking/lib/features/deep_linking/data/repositories/deep_link_repository_impl.dart.hbs +14 -0
  265. package/templates/modules/deep-linking/lib/features/deep_linking/domain/repositories/deep_link_repository.dart.hbs +7 -0
  266. package/templates/modules/deep-linking/lib/features/deep_linking/presentation/providers/deep_link_provider.dart.hbs +57 -0
  267. package/templates/modules/deep-linking/pubspec.partial.yaml +2 -0
  268. package/templates/modules/i18n/l10n.yaml.hbs +5 -0
  269. package/templates/modules/i18n/lib/core/l10n/l10n_config.dart.hbs +29 -0
  270. package/templates/modules/i18n/lib/core/l10n/l10n_provider.dart.hbs +20 -0
  271. package/templates/modules/i18n/lib/l10n/app_de.arb.hbs +27 -0
  272. package/templates/modules/i18n/lib/l10n/app_en.arb.hbs +27 -0
  273. package/templates/modules/i18n/pubspec.partial.yaml +7 -0
  274. package/templates/modules/push/lib/features/push/data/datasources/push_datasource.dart.hbs +124 -0
  275. package/templates/modules/push/lib/features/push/data/repositories/push_repository_impl.dart.hbs +28 -0
  276. package/templates/modules/push/lib/features/push/domain/repositories/push_repository.dart.hbs +31 -0
  277. package/templates/modules/push/lib/features/push/presentation/providers/push_provider.dart.hbs +34 -0
  278. package/templates/modules/push/pubspec.partial.yaml +8 -0
  279. package/templates/modules/theme/lib/core/theme/app_theme.dart.hbs +74 -0
  280. package/templates/modules/theme/lib/core/theme/theme_provider.dart.hbs +46 -0
  281. package/templates/modules/theme/pubspec.partial.yaml +2 -0
@@ -0,0 +1,31 @@
1
+ /// Typed exception for API errors with status code and message.
2
+ class ApiException implements Exception {
3
+ final int? statusCode;
4
+ final String message;
5
+ final dynamic data;
6
+
7
+ const ApiException({
8
+ this.statusCode,
9
+ required this.message,
10
+ this.data,
11
+ });
12
+
13
+ /// Creates an [ApiException] from a generic error.
14
+ factory ApiException.fromError(Object error) {
15
+ return ApiException(message: error.toString());
16
+ }
17
+
18
+ bool get isUnauthorized => statusCode == 401;
19
+ bool get isForbidden => statusCode == 403;
20
+ bool get isNotFound => statusCode == 404;
21
+ bool get isServerError => statusCode != null && statusCode! >= 500;
22
+ bool get isTimeout => statusCode == null && message.contains('timeout');
23
+
24
+ @override
25
+ String toString() {
26
+ if (statusCode != null) {
27
+ return 'ApiException($statusCode): $message';
28
+ }
29
+ return 'ApiException: $message';
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ /// Base repository interface for API operations.
2
+ ///
3
+ /// Extend this interface for feature-specific API repositories.
4
+ /// Implementations should use [ApiClient] for HTTP requests.
5
+ abstract class ApiRepository {
6
+ /// Perform a health check against the API.
7
+ Future<bool> healthCheck();
8
+ }
@@ -0,0 +1,17 @@
1
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
2
+ import 'package:riverpod_annotation/riverpod_annotation.dart';
3
+ import '../../data/datasources/api_client.dart';
4
+ import '../../data/repositories/api_repository_impl.dart';
5
+ import '../../domain/repositories/api_repository.dart';
6
+
7
+ part 'api_provider.g.dart';
8
+
9
+ @riverpod
10
+ ApiClient apiClient(Ref ref) {
11
+ return ApiClient();
12
+ }
13
+
14
+ @riverpod
15
+ ApiRepository apiRepository(Ref ref) {
16
+ return ApiRepositoryImpl(ref.watch(apiClientProvider));
17
+ }
@@ -0,0 +1,7 @@
1
+ dependencies:
2
+ dio: ^5.7.0
3
+ retrofit: ^4.4.1
4
+ json_annotation: ^4.9.0
5
+ dev_dependencies:
6
+ retrofit_generator: ^9.1.5
7
+ json_serializable: ^6.9.0
@@ -0,0 +1,181 @@
1
+ import '../models/auth_user_model.dart';
2
+ {{#ifEquals modules.auth.provider "firebase"}}
3
+ import 'package:firebase_auth/firebase_auth.dart' as firebase;
4
+ {{/ifEquals}}
5
+ {{#ifEquals modules.auth.provider "supabase"}}
6
+ import 'package:supabase_flutter/supabase_flutter.dart';
7
+ {{/ifEquals}}
8
+
9
+ abstract class AuthRemoteDataSource {
10
+ Stream<AuthUserModel?> get authStateChanges;
11
+ Future<AuthUserModel?> get currentUser;
12
+ Future<AuthUserModel> signInWithEmail(String email, String password);
13
+ Future<AuthUserModel> registerWithEmail(String email, String password, String? displayName);
14
+ Future<void> signOut();
15
+ Future<void> resetPassword(String email);
16
+ }
17
+ {{#ifEquals modules.auth.provider "firebase"}}
18
+
19
+ class FirebaseAuthDataSource implements AuthRemoteDataSource {
20
+ final firebase.FirebaseAuth _firebaseAuth;
21
+
22
+ FirebaseAuthDataSource({firebase.FirebaseAuth? firebaseAuth})
23
+ : _firebaseAuth = firebaseAuth ?? firebase.FirebaseAuth.instance;
24
+
25
+ AuthUserModel? _mapUser(firebase.User? user) {
26
+ if (user == null) return null;
27
+ return AuthUserModel(
28
+ id: user.uid,
29
+ email: user.email ?? '',
30
+ displayName: user.displayName,
31
+ photoUrl: user.photoURL,
32
+ emailVerified: user.emailVerified,
33
+ );
34
+ }
35
+
36
+ @override
37
+ Stream<AuthUserModel?> get authStateChanges =>
38
+ _firebaseAuth.authStateChanges().map(_mapUser);
39
+
40
+ @override
41
+ Future<AuthUserModel?> get currentUser async =>
42
+ _mapUser(_firebaseAuth.currentUser);
43
+
44
+ @override
45
+ Future<AuthUserModel> signInWithEmail(String email, String password) async {
46
+ final credential = await _firebaseAuth.signInWithEmailAndPassword(
47
+ email: email,
48
+ password: password,
49
+ );
50
+ return _mapUser(credential.user)!;
51
+ }
52
+
53
+ @override
54
+ Future<AuthUserModel> registerWithEmail(
55
+ String email,
56
+ String password,
57
+ String? displayName,
58
+ ) async {
59
+ final credential = await _firebaseAuth.createUserWithEmailAndPassword(
60
+ email: email,
61
+ password: password,
62
+ );
63
+ if (displayName != null) {
64
+ await credential.user?.updateDisplayName(displayName);
65
+ }
66
+ return _mapUser(credential.user)!;
67
+ }
68
+
69
+ @override
70
+ Future<void> signOut() => _firebaseAuth.signOut();
71
+
72
+ @override
73
+ Future<void> resetPassword(String email) =>
74
+ _firebaseAuth.sendPasswordResetEmail(email: email);
75
+ }
76
+ {{/ifEquals}}
77
+ {{#ifEquals modules.auth.provider "supabase"}}
78
+
79
+ class SupabaseAuthDataSource implements AuthRemoteDataSource {
80
+ final SupabaseClient _client;
81
+
82
+ SupabaseAuthDataSource({SupabaseClient? client})
83
+ : _client = client ?? Supabase.instance.client;
84
+
85
+ AuthUserModel? _mapUser(User? user) {
86
+ if (user == null) return null;
87
+ return AuthUserModel(
88
+ id: user.id,
89
+ email: user.email ?? '',
90
+ displayName: user.userMetadata?['display_name'] as String?,
91
+ emailVerified: user.emailConfirmedAt != null,
92
+ );
93
+ }
94
+
95
+ @override
96
+ Stream<AuthUserModel?> get authStateChanges =>
97
+ _client.auth.onAuthStateChange.map((event) => _mapUser(event.session?.user));
98
+
99
+ @override
100
+ Future<AuthUserModel?> get currentUser async =>
101
+ _mapUser(_client.auth.currentUser);
102
+
103
+ @override
104
+ Future<AuthUserModel> signInWithEmail(String email, String password) async {
105
+ final response = await _client.auth.signInWithPassword(
106
+ email: email,
107
+ password: password,
108
+ );
109
+ return _mapUser(response.user)!;
110
+ }
111
+
112
+ @override
113
+ Future<AuthUserModel> registerWithEmail(
114
+ String email,
115
+ String password,
116
+ String? displayName,
117
+ ) async {
118
+ final response = await _client.auth.signUp(
119
+ email: email,
120
+ password: password,
121
+ data: displayName != null ? {'display_name': displayName} : null,
122
+ );
123
+ return _mapUser(response.user)!;
124
+ }
125
+
126
+ @override
127
+ Future<void> signOut() => _client.auth.signOut();
128
+
129
+ @override
130
+ Future<void> resetPassword(String email) async {
131
+ await _client.auth.resetPasswordForEmail(email);
132
+ }
133
+ }
134
+ {{/ifEquals}}
135
+ {{#ifEquals modules.auth.provider "custom"}}
136
+
137
+ class CustomAuthDataSource implements AuthRemoteDataSource {
138
+ // TODO: Implement with your custom backend API.
139
+ // Inject your HTTP client (e.g., Dio) here.
140
+
141
+ @override
142
+ Stream<AuthUserModel?> get authStateChanges {
143
+ // TODO: Implement auth state stream (e.g., polling or WebSocket)
144
+ throw UnimplementedError('Implement auth state changes for custom backend');
145
+ }
146
+
147
+ @override
148
+ Future<AuthUserModel?> get currentUser {
149
+ // TODO: Implement current user check (e.g., validate stored token)
150
+ throw UnimplementedError('Implement current user for custom backend');
151
+ }
152
+
153
+ @override
154
+ Future<AuthUserModel> signInWithEmail(String email, String password) {
155
+ // TODO: POST to /auth/login
156
+ throw UnimplementedError('Implement sign in for custom backend');
157
+ }
158
+
159
+ @override
160
+ Future<AuthUserModel> registerWithEmail(
161
+ String email,
162
+ String password,
163
+ String? displayName,
164
+ ) {
165
+ // TODO: POST to /auth/register
166
+ throw UnimplementedError('Implement register for custom backend');
167
+ }
168
+
169
+ @override
170
+ Future<void> signOut() {
171
+ // TODO: POST to /auth/logout or clear tokens
172
+ throw UnimplementedError('Implement sign out for custom backend');
173
+ }
174
+
175
+ @override
176
+ Future<void> resetPassword(String email) {
177
+ // TODO: POST to /auth/reset-password
178
+ throw UnimplementedError('Implement reset password for custom backend');
179
+ }
180
+ }
181
+ {{/ifEquals}}
@@ -0,0 +1,27 @@
1
+ import '../../domain/entities/user_entity.dart';
2
+
3
+ class AuthUserModel {
4
+ final String id;
5
+ final String email;
6
+ final String? displayName;
7
+ final String? photoUrl;
8
+ final bool emailVerified;
9
+
10
+ const AuthUserModel({
11
+ required this.id,
12
+ required this.email,
13
+ this.displayName,
14
+ this.photoUrl,
15
+ this.emailVerified = false,
16
+ });
17
+
18
+ UserEntity toEntity() {
19
+ return UserEntity(
20
+ id: id,
21
+ email: email,
22
+ displayName: displayName,
23
+ photoUrl: photoUrl,
24
+ emailVerified: emailVerified,
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,49 @@
1
+ import '../../domain/entities/user_entity.dart';
2
+ import '../../domain/repositories/auth_repository.dart';
3
+ import '../datasources/auth_remote_datasource.dart';
4
+
5
+ class AuthRepositoryImpl implements AuthRepository {
6
+ final AuthRemoteDataSource _remoteDataSource;
7
+
8
+ const AuthRepositoryImpl(this._remoteDataSource);
9
+
10
+ @override
11
+ Stream<UserEntity?> get authStateChanges =>
12
+ _remoteDataSource.authStateChanges.map((model) => model?.toEntity());
13
+
14
+ @override
15
+ Future<UserEntity?> get currentUser async {
16
+ final model = await _remoteDataSource.currentUser;
17
+ return model?.toEntity();
18
+ }
19
+
20
+ @override
21
+ Future<UserEntity> signInWithEmail({
22
+ required String email,
23
+ required String password,
24
+ }) async {
25
+ final model = await _remoteDataSource.signInWithEmail(email, password);
26
+ return model.toEntity();
27
+ }
28
+
29
+ @override
30
+ Future<UserEntity> registerWithEmail({
31
+ required String email,
32
+ required String password,
33
+ String? displayName,
34
+ }) async {
35
+ final model = await _remoteDataSource.registerWithEmail(
36
+ email,
37
+ password,
38
+ displayName,
39
+ );
40
+ return model.toEntity();
41
+ }
42
+
43
+ @override
44
+ Future<void> signOut() => _remoteDataSource.signOut();
45
+
46
+ @override
47
+ Future<void> resetPassword({required String email}) =>
48
+ _remoteDataSource.resetPassword(email);
49
+ }
@@ -0,0 +1,18 @@
1
+ import 'package:freezed_annotation/freezed_annotation.dart';
2
+
3
+ part 'user_entity.freezed.dart';
4
+ part 'user_entity.g.dart';
5
+
6
+ @freezed
7
+ class UserEntity with _$UserEntity {
8
+ const factory UserEntity({
9
+ required String id,
10
+ required String email,
11
+ String? displayName,
12
+ String? photoUrl,
13
+ @Default(false) bool emailVerified,
14
+ }) = _UserEntity;
15
+
16
+ factory UserEntity.fromJson(Map<String, dynamic> json) =>
17
+ _$UserEntityFromJson(json);
18
+ }
@@ -0,0 +1,22 @@
1
+ import '../entities/user_entity.dart';
2
+
3
+ abstract class AuthRepository {
4
+ Stream<UserEntity?> get authStateChanges;
5
+
6
+ Future<UserEntity?> get currentUser;
7
+
8
+ Future<UserEntity> signInWithEmail({
9
+ required String email,
10
+ required String password,
11
+ });
12
+
13
+ Future<UserEntity> registerWithEmail({
14
+ required String email,
15
+ required String password,
16
+ String? displayName,
17
+ });
18
+
19
+ Future<void> signOut();
20
+
21
+ Future<void> resetPassword({required String email});
22
+ }
@@ -0,0 +1,20 @@
1
+ import '../entities/user_entity.dart';
2
+ import '../repositories/auth_repository.dart';
3
+
4
+ class RegisterUseCase {
5
+ final AuthRepository _repository;
6
+
7
+ const RegisterUseCase(this._repository);
8
+
9
+ Future<UserEntity> call({
10
+ required String email,
11
+ required String password,
12
+ String? displayName,
13
+ }) {
14
+ return _repository.registerWithEmail(
15
+ email: email,
16
+ password: password,
17
+ displayName: displayName,
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,15 @@
1
+ import '../entities/user_entity.dart';
2
+ import '../repositories/auth_repository.dart';
3
+
4
+ class SignInUseCase {
5
+ final AuthRepository _repository;
6
+
7
+ const SignInUseCase(this._repository);
8
+
9
+ Future<UserEntity> call({
10
+ required String email,
11
+ required String password,
12
+ }) {
13
+ return _repository.signInWithEmail(email: email, password: password);
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ import '../repositories/auth_repository.dart';
2
+
3
+ class SignOutUseCase {
4
+ final AuthRepository _repository;
5
+
6
+ const SignOutUseCase(this._repository);
7
+
8
+ Future<void> call() {
9
+ return _repository.signOut();
10
+ }
11
+ }
@@ -0,0 +1,118 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:go_router/go_router.dart';
4
+ import '../providers/auth_provider.dart';
5
+
6
+ class LoginPage extends ConsumerStatefulWidget {
7
+ const LoginPage({super.key});
8
+
9
+ @override
10
+ ConsumerState<LoginPage> createState() => _LoginPageState();
11
+ }
12
+
13
+ class _LoginPageState extends ConsumerState<LoginPage> {
14
+ final _formKey = GlobalKey<FormState>();
15
+ final _emailController = TextEditingController();
16
+ final _passwordController = TextEditingController();
17
+ bool _isLoading = false;
18
+
19
+ @override
20
+ void dispose() {
21
+ _emailController.dispose();
22
+ _passwordController.dispose();
23
+ super.dispose();
24
+ }
25
+
26
+ Future<void> _handleLogin() async {
27
+ if (!_formKey.currentState!.validate()) return;
28
+
29
+ setState(() => _isLoading = true);
30
+
31
+ try {
32
+ final repository = ref.read(authRepositoryProvider);
33
+ await repository.signInWithEmail(
34
+ email: _emailController.text.trim(),
35
+ password: _passwordController.text,
36
+ );
37
+ if (mounted) {
38
+ context.go('/');
39
+ }
40
+ } catch (e) {
41
+ if (mounted) {
42
+ ScaffoldMessenger.of(context).showSnackBar(
43
+ SnackBar(content: Text('Login failed: $e')),
44
+ );
45
+ }
46
+ } finally {
47
+ if (mounted) {
48
+ setState(() => _isLoading = false);
49
+ }
50
+ }
51
+ }
52
+
53
+ @override
54
+ Widget build(BuildContext context) {
55
+ return Scaffold(
56
+ appBar: AppBar(title: const Text('Login')),
57
+ body: Padding(
58
+ padding: const EdgeInsets.all(16.0),
59
+ child: Form(
60
+ key: _formKey,
61
+ child: Column(
62
+ mainAxisAlignment: MainAxisAlignment.center,
63
+ children: [
64
+ TextFormField(
65
+ controller: _emailController,
66
+ decoration: const InputDecoration(
67
+ labelText: 'Email',
68
+ border: OutlineInputBorder(),
69
+ ),
70
+ keyboardType: TextInputType.emailAddress,
71
+ validator: (value) {
72
+ if (value == null || value.isEmpty) {
73
+ return 'Please enter your email';
74
+ }
75
+ return null;
76
+ },
77
+ ),
78
+ const SizedBox(height: 16),
79
+ TextFormField(
80
+ controller: _passwordController,
81
+ decoration: const InputDecoration(
82
+ labelText: 'Password',
83
+ border: OutlineInputBorder(),
84
+ ),
85
+ obscureText: true,
86
+ validator: (value) {
87
+ if (value == null || value.isEmpty) {
88
+ return 'Please enter your password';
89
+ }
90
+ return null;
91
+ },
92
+ ),
93
+ const SizedBox(height: 24),
94
+ SizedBox(
95
+ width: double.infinity,
96
+ child: FilledButton(
97
+ onPressed: _isLoading ? null : _handleLogin,
98
+ child: _isLoading
99
+ ? const SizedBox(
100
+ height: 20,
101
+ width: 20,
102
+ child: CircularProgressIndicator(strokeWidth: 2),
103
+ )
104
+ : const Text('Login'),
105
+ ),
106
+ ),
107
+ const SizedBox(height: 16),
108
+ TextButton(
109
+ onPressed: () => context.go('/register'),
110
+ child: const Text("Don't have an account? Register"),
111
+ ),
112
+ ],
113
+ ),
114
+ ),
115
+ ),
116
+ );
117
+ }
118
+ }
@@ -0,0 +1,131 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:go_router/go_router.dart';
4
+ import '../providers/auth_provider.dart';
5
+
6
+ class RegisterPage extends ConsumerStatefulWidget {
7
+ const RegisterPage({super.key});
8
+
9
+ @override
10
+ ConsumerState<RegisterPage> createState() => _RegisterPageState();
11
+ }
12
+
13
+ class _RegisterPageState extends ConsumerState<RegisterPage> {
14
+ final _formKey = GlobalKey<FormState>();
15
+ final _nameController = TextEditingController();
16
+ final _emailController = TextEditingController();
17
+ final _passwordController = TextEditingController();
18
+ bool _isLoading = false;
19
+
20
+ @override
21
+ void dispose() {
22
+ _nameController.dispose();
23
+ _emailController.dispose();
24
+ _passwordController.dispose();
25
+ super.dispose();
26
+ }
27
+
28
+ Future<void> _handleRegister() async {
29
+ if (!_formKey.currentState!.validate()) return;
30
+
31
+ setState(() => _isLoading = true);
32
+
33
+ try {
34
+ final repository = ref.read(authRepositoryProvider);
35
+ await repository.registerWithEmail(
36
+ email: _emailController.text.trim(),
37
+ password: _passwordController.text,
38
+ displayName: _nameController.text.trim().isNotEmpty
39
+ ? _nameController.text.trim()
40
+ : null,
41
+ );
42
+ if (mounted) {
43
+ context.go('/');
44
+ }
45
+ } catch (e) {
46
+ if (mounted) {
47
+ ScaffoldMessenger.of(context).showSnackBar(
48
+ SnackBar(content: Text('Registration failed: $e')),
49
+ );
50
+ }
51
+ } finally {
52
+ if (mounted) {
53
+ setState(() => _isLoading = false);
54
+ }
55
+ }
56
+ }
57
+
58
+ @override
59
+ Widget build(BuildContext context) {
60
+ return Scaffold(
61
+ appBar: AppBar(title: const Text('Register')),
62
+ body: Padding(
63
+ padding: const EdgeInsets.all(16.0),
64
+ child: Form(
65
+ key: _formKey,
66
+ child: Column(
67
+ mainAxisAlignment: MainAxisAlignment.center,
68
+ children: [
69
+ TextFormField(
70
+ controller: _nameController,
71
+ decoration: const InputDecoration(
72
+ labelText: 'Display Name (optional)',
73
+ border: OutlineInputBorder(),
74
+ ),
75
+ ),
76
+ const SizedBox(height: 16),
77
+ TextFormField(
78
+ controller: _emailController,
79
+ decoration: const InputDecoration(
80
+ labelText: 'Email',
81
+ border: OutlineInputBorder(),
82
+ ),
83
+ keyboardType: TextInputType.emailAddress,
84
+ validator: (value) {
85
+ if (value == null || value.isEmpty) {
86
+ return 'Please enter your email';
87
+ }
88
+ return null;
89
+ },
90
+ ),
91
+ const SizedBox(height: 16),
92
+ TextFormField(
93
+ controller: _passwordController,
94
+ decoration: const InputDecoration(
95
+ labelText: 'Password',
96
+ border: OutlineInputBorder(),
97
+ ),
98
+ obscureText: true,
99
+ validator: (value) {
100
+ if (value == null || value.length < 6) {
101
+ return 'Password must be at least 6 characters';
102
+ }
103
+ return null;
104
+ },
105
+ ),
106
+ const SizedBox(height: 24),
107
+ SizedBox(
108
+ width: double.infinity,
109
+ child: FilledButton(
110
+ onPressed: _isLoading ? null : _handleRegister,
111
+ child: _isLoading
112
+ ? const SizedBox(
113
+ height: 20,
114
+ width: 20,
115
+ child: CircularProgressIndicator(strokeWidth: 2),
116
+ )
117
+ : const Text('Register'),
118
+ ),
119
+ ),
120
+ const SizedBox(height: 16),
121
+ TextButton(
122
+ onPressed: () => context.go('/login'),
123
+ child: const Text('Already have an account? Login'),
124
+ ),
125
+ ],
126
+ ),
127
+ ),
128
+ ),
129
+ );
130
+ }
131
+ }