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.
- package/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +445 -0
- package/claude-plugin/.claude-plugin/plugin.json +46 -0
- package/claude-plugin/agents/flutter-setup-agent.md +54 -0
- package/claude-plugin/commands/flutter-add.md +32 -0
- package/claude-plugin/commands/flutter-create.md +28 -0
- package/claude-plugin/commands/flutter-migrate.md +28 -0
- package/claude-plugin/skills/flutter-scaffolding/SKILL.md +109 -0
- package/dist/claude-setup/agent-writer.d.ts +21 -0
- package/dist/claude-setup/agent-writer.d.ts.map +1 -0
- package/dist/claude-setup/agent-writer.js +365 -0
- package/dist/claude-setup/agent-writer.js.map +1 -0
- package/dist/claude-setup/claude-md-generator.d.ts +7 -0
- package/dist/claude-setup/claude-md-generator.d.ts.map +1 -0
- package/dist/claude-setup/claude-md-generator.js +428 -0
- package/dist/claude-setup/claude-md-generator.js.map +1 -0
- package/dist/claude-setup/commands-writer.d.ts +7 -0
- package/dist/claude-setup/commands-writer.d.ts.map +1 -0
- package/dist/claude-setup/commands-writer.js +303 -0
- package/dist/claude-setup/commands-writer.js.map +1 -0
- package/dist/claude-setup/hooks-writer.d.ts +3 -0
- package/dist/claude-setup/hooks-writer.d.ts.map +1 -0
- package/dist/claude-setup/hooks-writer.js +34 -0
- package/dist/claude-setup/hooks-writer.js.map +1 -0
- package/dist/claude-setup/index.d.ts +10 -0
- package/dist/claude-setup/index.d.ts.map +1 -0
- package/dist/claude-setup/index.js +9 -0
- package/dist/claude-setup/index.js.map +1 -0
- package/dist/claude-setup/mcp-config-writer.d.ts +3 -0
- package/dist/claude-setup/mcp-config-writer.d.ts.map +1 -0
- package/dist/claude-setup/mcp-config-writer.js +42 -0
- package/dist/claude-setup/mcp-config-writer.js.map +1 -0
- package/dist/claude-setup/plugin-assembler.d.ts +2 -0
- package/dist/claude-setup/plugin-assembler.d.ts.map +1 -0
- package/dist/claude-setup/plugin-assembler.js +3 -0
- package/dist/claude-setup/plugin-assembler.js.map +1 -0
- package/dist/claude-setup/prd-generator.d.ts +10 -0
- package/dist/claude-setup/prd-generator.d.ts.map +1 -0
- package/dist/claude-setup/prd-generator.js +295 -0
- package/dist/claude-setup/prd-generator.js.map +1 -0
- package/dist/claude-setup/setup-orchestrator.d.ts +11 -0
- package/dist/claude-setup/setup-orchestrator.d.ts.map +1 -0
- package/dist/claude-setup/setup-orchestrator.js +46 -0
- package/dist/claude-setup/setup-orchestrator.js.map +1 -0
- package/dist/claude-setup/skill-writer.d.ts +3 -0
- package/dist/claude-setup/skill-writer.d.ts.map +1 -0
- package/dist/claude-setup/skill-writer.js +256 -0
- package/dist/claude-setup/skill-writer.js.map +1 -0
- package/dist/cli/commands/add.d.ts +16 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/add.js +414 -0
- package/dist/cli/commands/add.js.map +1 -0
- package/dist/cli/commands/create.d.ts +3 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +161 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/list.d.ts +12 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +148 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +3 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +332 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +34 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/prompts.d.ts +9 -0
- package/dist/cli/ui/prompts.d.ts.map +1 -0
- package/dist/cli/ui/prompts.js +74 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +2 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +5 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/cli/version-check.d.ts +7 -0
- package/dist/cli/version-check.d.ts.map +1 -0
- package/dist/cli/version-check.js +92 -0
- package/dist/cli/version-check.js.map +1 -0
- package/dist/core/config/defaults.d.ts +3 -0
- package/dist/core/config/defaults.d.ts.map +1 -0
- package/dist/core/config/defaults.js +21 -0
- package/dist/core/config/defaults.js.map +1 -0
- package/dist/core/config/loader.d.ts +4 -0
- package/dist/core/config/loader.d.ts.map +1 -0
- package/dist/core/config/loader.js +38 -0
- package/dist/core/config/loader.js.map +1 -0
- package/dist/core/config/schema.d.ts +406 -0
- package/dist/core/config/schema.d.ts.map +1 -0
- package/dist/core/config/schema.js +119 -0
- package/dist/core/config/schema.js.map +1 -0
- package/dist/core/context.d.ts +57 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +99 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/detector.d.ts +28 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +200 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/validator.d.ts +7 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +35 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/composer.d.ts +50 -0
- package/dist/modules/composer.d.ts.map +1 -0
- package/dist/modules/composer.js +122 -0
- package/dist/modules/composer.js.map +1 -0
- package/dist/modules/definitions/analytics/module.d.ts +7 -0
- package/dist/modules/definitions/analytics/module.d.ts.map +1 -0
- package/dist/modules/definitions/analytics/module.js +28 -0
- package/dist/modules/definitions/analytics/module.js.map +1 -0
- package/dist/modules/definitions/api/module.d.ts +7 -0
- package/dist/modules/definitions/api/module.d.ts.map +1 -0
- package/dist/modules/definitions/api/module.js +41 -0
- package/dist/modules/definitions/api/module.js.map +1 -0
- package/dist/modules/definitions/auth/module.d.ts +7 -0
- package/dist/modules/definitions/auth/module.d.ts.map +1 -0
- package/dist/modules/definitions/auth/module.js +53 -0
- package/dist/modules/definitions/auth/module.js.map +1 -0
- package/dist/modules/definitions/cicd/module.d.ts +7 -0
- package/dist/modules/definitions/cicd/module.d.ts.map +1 -0
- package/dist/modules/definitions/cicd/module.js +34 -0
- package/dist/modules/definitions/cicd/module.js.map +1 -0
- package/dist/modules/definitions/core/module.d.ts +7 -0
- package/dist/modules/definitions/core/module.d.ts.map +1 -0
- package/dist/modules/definitions/core/module.js +31 -0
- package/dist/modules/definitions/core/module.js.map +1 -0
- package/dist/modules/definitions/database/module.d.ts +8 -0
- package/dist/modules/definitions/database/module.d.ts.map +1 -0
- package/dist/modules/definitions/database/module.js +47 -0
- package/dist/modules/definitions/database/module.js.map +1 -0
- package/dist/modules/definitions/deep-linking/module.d.ts +7 -0
- package/dist/modules/definitions/deep-linking/module.d.ts.map +1 -0
- package/dist/modules/definitions/deep-linking/module.js +48 -0
- package/dist/modules/definitions/deep-linking/module.js.map +1 -0
- package/dist/modules/definitions/i18n/module.d.ts +7 -0
- package/dist/modules/definitions/i18n/module.d.ts.map +1 -0
- package/dist/modules/definitions/i18n/module.js +37 -0
- package/dist/modules/definitions/i18n/module.js.map +1 -0
- package/dist/modules/definitions/push/module.d.ts +7 -0
- package/dist/modules/definitions/push/module.d.ts.map +1 -0
- package/dist/modules/definitions/push/module.js +40 -0
- package/dist/modules/definitions/push/module.js.map +1 -0
- package/dist/modules/definitions/theme/module.d.ts +7 -0
- package/dist/modules/definitions/theme/module.d.ts.map +1 -0
- package/dist/modules/definitions/theme/module.js +42 -0
- package/dist/modules/definitions/theme/module.js.map +1 -0
- package/dist/modules/registry.d.ts +50 -0
- package/dist/modules/registry.d.ts.map +1 -0
- package/dist/modules/registry.js +104 -0
- package/dist/modules/registry.js.map +1 -0
- package/dist/modules/resolver.d.ts +42 -0
- package/dist/modules/resolver.d.ts.map +1 -0
- package/dist/modules/resolver.js +140 -0
- package/dist/modules/resolver.js.map +1 -0
- package/dist/ralph/prd-generator.d.ts +2 -0
- package/dist/ralph/prd-generator.d.ts.map +1 -0
- package/dist/ralph/prd-generator.js +3 -0
- package/dist/ralph/prd-generator.js.map +1 -0
- package/dist/ralph/ralph-converter.d.ts +2 -0
- package/dist/ralph/ralph-converter.d.ts.map +1 -0
- package/dist/ralph/ralph-converter.js +3 -0
- package/dist/ralph/ralph-converter.js.map +1 -0
- package/dist/ralph/story-sizer.d.ts +2 -0
- package/dist/ralph/story-sizer.d.ts.map +1 -0
- package/dist/ralph/story-sizer.js +3 -0
- package/dist/ralph/story-sizer.js.map +1 -0
- package/dist/scaffold/engine.d.ts +40 -0
- package/dist/scaffold/engine.d.ts.map +1 -0
- package/dist/scaffold/engine.js +233 -0
- package/dist/scaffold/engine.js.map +1 -0
- package/dist/scaffold/file-writer.d.ts +22 -0
- package/dist/scaffold/file-writer.d.ts.map +1 -0
- package/dist/scaffold/file-writer.js +68 -0
- package/dist/scaffold/file-writer.js.map +1 -0
- package/dist/scaffold/post-processors/build-runner.d.ts +2 -0
- package/dist/scaffold/post-processors/build-runner.d.ts.map +1 -0
- package/dist/scaffold/post-processors/build-runner.js +5 -0
- package/dist/scaffold/post-processors/build-runner.js.map +1 -0
- package/dist/scaffold/post-processors/dart-format.d.ts +2 -0
- package/dist/scaffold/post-processors/dart-format.d.ts.map +1 -0
- package/dist/scaffold/post-processors/dart-format.js +5 -0
- package/dist/scaffold/post-processors/dart-format.js.map +1 -0
- package/dist/scaffold/post-processors/flutter-pub-get.d.ts +2 -0
- package/dist/scaffold/post-processors/flutter-pub-get.d.ts.map +1 -0
- package/dist/scaffold/post-processors/flutter-pub-get.js +5 -0
- package/dist/scaffold/post-processors/flutter-pub-get.js.map +1 -0
- package/dist/scaffold/renderer.d.ts +20 -0
- package/dist/scaffold/renderer.d.ts.map +1 -0
- package/dist/scaffold/renderer.js +75 -0
- package/dist/scaffold/renderer.js.map +1 -0
- package/dist/scaffold/template-helpers.d.ts +25 -0
- package/dist/scaffold/template-helpers.d.ts.map +1 -0
- package/dist/scaffold/template-helpers.js +115 -0
- package/dist/scaffold/template-helpers.js.map +1 -0
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/module.d.ts +85 -0
- package/dist/types/module.d.ts.map +1 -0
- package/dist/types/module.js +2 -0
- package/dist/types/module.js.map +1 -0
- package/dist/types/project.d.ts +13 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +2 -0
- package/dist/types/project.js.map +1 -0
- package/package.json +80 -0
- package/templates/core/analysis_options.yaml.hbs +21 -0
- package/templates/core/lib/app.dart.hbs +20 -0
- package/templates/core/lib/core/providers/app_providers.dart.hbs +3 -0
- package/templates/core/lib/core/router/app_router.dart.hbs +19 -0
- package/templates/core/lib/core/theme/app_theme.dart.hbs +15 -0
- package/templates/core/lib/features/home/data/models/.gitkeep +0 -0
- package/templates/core/lib/features/home/data/repositories/.gitkeep +0 -0
- package/templates/core/lib/features/home/domain/entities/.gitkeep +0 -0
- package/templates/core/lib/features/home/domain/repositories/.gitkeep +0 -0
- package/templates/core/lib/features/home/presentation/pages/home_page.dart.hbs +18 -0
- package/templates/core/lib/features/home/presentation/widgets/.gitkeep +0 -0
- package/templates/core/lib/main.dart.hbs +11 -0
- package/templates/core/pubspec.yaml.hbs +30 -0
- package/templates/core/test/widget_test.dart.hbs +16 -0
- package/templates/modules/analytics/lib/features/analytics/data/observers/analytics_route_observer.dart.hbs +53 -0
- package/templates/modules/analytics/lib/features/analytics/data/services/firebase_analytics_service.dart.hbs +39 -0
- package/templates/modules/analytics/lib/features/analytics/domain/services/analytics_service.dart.hbs +25 -0
- package/templates/modules/analytics/lib/features/analytics/presentation/providers/analytics_provider.dart.hbs +17 -0
- package/templates/modules/analytics/pubspec.partial.yaml +2 -0
- package/templates/modules/api/lib/features/api/data/datasources/api_client.dart.hbs +171 -0
- package/templates/modules/api/lib/features/api/data/interceptors/auth_interceptor.dart.hbs +25 -0
- package/templates/modules/api/lib/features/api/data/interceptors/retry_interceptor.dart.hbs +42 -0
- package/templates/modules/api/lib/features/api/data/repositories/api_repository_impl.dart.hbs +21 -0
- package/templates/modules/api/lib/features/api/domain/exceptions/api_exception.dart.hbs +31 -0
- package/templates/modules/api/lib/features/api/domain/repositories/api_repository.dart.hbs +8 -0
- package/templates/modules/api/lib/features/api/presentation/providers/api_provider.dart.hbs +17 -0
- package/templates/modules/api/pubspec.partial.yaml +7 -0
- package/templates/modules/auth/lib/features/auth/data/datasources/auth_remote_datasource.dart.hbs +181 -0
- package/templates/modules/auth/lib/features/auth/data/models/auth_user_model.dart.hbs +27 -0
- package/templates/modules/auth/lib/features/auth/data/repositories/auth_repository_impl.dart.hbs +49 -0
- package/templates/modules/auth/lib/features/auth/domain/entities/user_entity.dart.hbs +18 -0
- package/templates/modules/auth/lib/features/auth/domain/repositories/auth_repository.dart.hbs +22 -0
- package/templates/modules/auth/lib/features/auth/domain/usecases/register_usecase.dart.hbs +20 -0
- package/templates/modules/auth/lib/features/auth/domain/usecases/sign_in_usecase.dart.hbs +15 -0
- package/templates/modules/auth/lib/features/auth/domain/usecases/sign_out_usecase.dart.hbs +11 -0
- package/templates/modules/auth/lib/features/auth/presentation/pages/login_page.dart.hbs +118 -0
- package/templates/modules/auth/lib/features/auth/presentation/pages/register_page.dart.hbs +131 -0
- package/templates/modules/auth/lib/features/auth/presentation/providers/auth_provider.dart.hbs +31 -0
- package/templates/modules/auth/pubspec.partial.yaml +13 -0
- package/templates/modules/cicd/.github/workflows/ci.yml.hbs +45 -0
- package/templates/modules/cicd/.gitlab-ci.yml.hbs +53 -0
- package/templates/modules/cicd/bitbucket-pipelines.yml.hbs +39 -0
- package/templates/modules/cicd/pubspec.partial.yaml +1 -0
- package/templates/modules/database/lib/features/database/data/datasources/database_datasource.dart.hbs +115 -0
- package/templates/modules/database/lib/features/database/data/repositories/database_repository_impl.dart.hbs +73 -0
- package/templates/modules/database/lib/features/database/domain/repositories/database_repository.dart.hbs +10 -0
- package/templates/modules/database/lib/features/database/presentation/providers/database_provider.dart.hbs +43 -0
- package/templates/modules/database/pubspec.partial.yaml +27 -0
- package/templates/modules/deep-linking/lib/features/deep_linking/data/datasources/deep_link_datasource.dart.hbs +18 -0
- package/templates/modules/deep-linking/lib/features/deep_linking/data/repositories/deep_link_repository_impl.dart.hbs +14 -0
- package/templates/modules/deep-linking/lib/features/deep_linking/domain/repositories/deep_link_repository.dart.hbs +7 -0
- package/templates/modules/deep-linking/lib/features/deep_linking/presentation/providers/deep_link_provider.dart.hbs +57 -0
- package/templates/modules/deep-linking/pubspec.partial.yaml +2 -0
- package/templates/modules/i18n/l10n.yaml.hbs +5 -0
- package/templates/modules/i18n/lib/core/l10n/l10n_config.dart.hbs +29 -0
- package/templates/modules/i18n/lib/core/l10n/l10n_provider.dart.hbs +20 -0
- package/templates/modules/i18n/lib/l10n/app_de.arb.hbs +27 -0
- package/templates/modules/i18n/lib/l10n/app_en.arb.hbs +27 -0
- package/templates/modules/i18n/pubspec.partial.yaml +7 -0
- package/templates/modules/push/lib/features/push/data/datasources/push_datasource.dart.hbs +124 -0
- package/templates/modules/push/lib/features/push/data/repositories/push_repository_impl.dart.hbs +28 -0
- package/templates/modules/push/lib/features/push/domain/repositories/push_repository.dart.hbs +31 -0
- package/templates/modules/push/lib/features/push/presentation/providers/push_provider.dart.hbs +34 -0
- package/templates/modules/push/pubspec.partial.yaml +8 -0
- package/templates/modules/theme/lib/core/theme/app_theme.dart.hbs +74 -0
- package/templates/modules/theme/lib/core/theme/theme_provider.dart.hbs +46 -0
- 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
|
+
}
|
package/templates/modules/auth/lib/features/auth/data/datasources/auth_remote_datasource.dart.hbs
ADDED
|
@@ -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
|
+
}
|
package/templates/modules/auth/lib/features/auth/data/repositories/auth_repository_impl.dart.hbs
ADDED
|
@@ -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,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
|
+
}
|