Compare commits
31 Commits
architectu
...
onboarding
| Author | SHA1 | Date | |
|---|---|---|---|
| ae429302bb | |||
| 1f3de3df26 | |||
| bbe77f6a8a | |||
|
|
760e94ffe9 | ||
|
|
ad10ad3b59 | ||
| baef98a443 | |||
| b8bb3e65c2 | |||
| cbc991b2fd | |||
|
|
6b3776f618 | ||
| 7bfc4039ab | |||
| 16e3c68d1a | |||
| 4869850c43 | |||
| cb7ff71756 | |||
| 99f544e24c | |||
|
|
14a925659a | ||
| 91e47f28fb | |||
|
|
07a0fd5695 | ||
|
|
b823b422f0 | ||
|
|
a64a9a2a32 | ||
|
|
9d7f940851 | ||
| 14d0f1515b | |||
| 1b0517344e | |||
| ae0156f3e6 | |||
| 34cb4ebf69 | |||
|
|
6c5da2d492 | ||
| ca7c3755c2 | |||
| a5dd644a62 | |||
| 73a2c46999 | |||
| 62ffc9ef7c | |||
| 11c4c5ab07 | |||
| 8201bff0a7 |
@@ -1,31 +0,0 @@
|
|||||||
Extension Discovery Cache
|
|
||||||
=========================
|
|
||||||
|
|
||||||
This folder is used by `package:extension_discovery` to cache lists of
|
|
||||||
packages that contains extensions for other packages.
|
|
||||||
|
|
||||||
DO NOT USE THIS FOLDER
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
* Do not read (or rely) the contents of this folder.
|
|
||||||
* Do write to this folder.
|
|
||||||
|
|
||||||
If you're interested in the lists of extensions stored in this folder use the
|
|
||||||
API offered by package `extension_discovery` to get this information.
|
|
||||||
|
|
||||||
If this package doesn't work for your use-case, then don't try to read the
|
|
||||||
contents of this folder. It may change, and will not remain stable.
|
|
||||||
|
|
||||||
Use package `extension_discovery`
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
If you want to access information from this folder.
|
|
||||||
|
|
||||||
Feel free to delete this folder
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Files in this folder act as a cache, and the cache is discarded if the files
|
|
||||||
are older than the modification time of `.dart_tool/package_config.json`.
|
|
||||||
|
|
||||||
Hence, it should never be necessary to clear this cache manually, if you find a
|
|
||||||
need to do please file a bug.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":2,"entries":[{"package":"sf_app_platform_mono_repo","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"workspaceRoot": "../.."
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2025-11-10 13:39:20.696891","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2025-12-04 12:33:12.174628","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||||
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -5,11 +5,13 @@
|
|||||||
<module fileurl="file://$PROJECT_DIR$/modules/auth/melos_auth.iml" filepath="$PROJECT_DIR$/modules/auth/melos_auth.iml" />
|
<module fileurl="file://$PROJECT_DIR$/modules/auth/melos_auth.iml" filepath="$PROJECT_DIR$/modules/auth/melos_auth.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" filepath="$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" />
|
<module fileurl="file://$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" filepath="$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/packages/design_system/melos_design_system.iml" filepath="$PROJECT_DIR$/packages/design_system/melos_design_system.iml" />
|
<module fileurl="file://$PROJECT_DIR$/packages/design_system/melos_design_system.iml" filepath="$PROJECT_DIR$/packages/design_system/melos_design_system.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/packages/fonts/melos_fonts.iml" filepath="$PROJECT_DIR$/packages/fonts/melos_fonts.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/modules/home/melos_home.iml" filepath="$PROJECT_DIR$/modules/home/melos_home.iml" />
|
<module fileurl="file://$PROJECT_DIR$/modules/home/melos_home.iml" filepath="$PROJECT_DIR$/modules/home/melos_home.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/packages/navigation/melos_navigation.iml" filepath="$PROJECT_DIR$/packages/navigation/melos_navigation.iml" />
|
<module fileurl="file://$PROJECT_DIR$/packages/navigation/melos_navigation.iml" filepath="$PROJECT_DIR$/packages/navigation/melos_navigation.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/modules/notifications/melos_notifications.iml" filepath="$PROJECT_DIR$/modules/notifications/melos_notifications.iml" />
|
<module fileurl="file://$PROJECT_DIR$/modules/notifications/melos_notifications.iml" filepath="$PROJECT_DIR$/modules/notifications/melos_notifications.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/modules/profile/melos_profile.iml" filepath="$PROJECT_DIR$/modules/profile/melos_profile.iml" />
|
<module fileurl="file://$PROJECT_DIR$/modules/profile/melos_profile.iml" filepath="$PROJECT_DIR$/modules/profile/melos_profile.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" filepath="$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" />
|
<module fileurl="file://$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" filepath="$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/packages/sf_infrastructure/melos_sf_infrastructure.iml" filepath="$PROJECT_DIR$/packages/sf_infrastructure/melos_sf_infrastructure.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" filepath="$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" />
|
<module fileurl="file://$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" filepath="$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" filepath="$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" />
|
<module fileurl="file://$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" filepath="$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/packages/utils/melos_utils.iml" filepath="$PROJECT_DIR$/packages/utils/melos_utils.iml" />
|
<module fileurl="file://$PROJECT_DIR$/packages/utils/melos_utils.iml" filepath="$PROJECT_DIR$/packages/utils/melos_utils.iml" />
|
||||||
|
|||||||
7
.idea/runConfigurations/melos_flutter_test_sf_infrastructure.xml
generated
Normal file
7
.idea/runConfigurations/melos_flutter_test_sf_infrastructure.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!-- Generated by Melos -->
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Flutter Test -> 'sf_infrastructure'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||||
|
<option name="testDir" value="$PROJECT_DIR$/packages/sf_infrastructure/test" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
1
apps/mobile_app/.env
Normal file
1
apps/mobile_app/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
API_BASE_URL=https://api-neki-b2b.neki.es/gateway/api/
|
||||||
7
apps/mobile_app/lib/config/env/env.dart
vendored
Normal file
7
apps/mobile_app/lib/config/env/env.dart
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
|
||||||
|
class Env {
|
||||||
|
static String get apiBaseUrl => dotenv.env['API_BASE_URL'] ?? '';
|
||||||
|
|
||||||
|
// static String get apiKey => dotenv.env['API_KEY'] ?? '';
|
||||||
|
}
|
||||||
10
apps/mobile_app/lib/config/env/questia_env_config.dart
vendored
Normal file
10
apps/mobile_app/lib/config/env/questia_env_config.dart
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
import 'env.dart';
|
||||||
|
|
||||||
|
class QuestiaEnvConfig implements EnvConfig {
|
||||||
|
@override
|
||||||
|
String get apiBaseUrl => Env.apiBaseUrl;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// String get apiKey => Env.apiKey;
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:sf_app_platform/config/env/questia_env_config.dart';
|
||||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||||
import 'package:navigation/navigation_module.dart';
|
import 'package:navigation/navigation_module.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
@@ -11,7 +15,9 @@ Future<void> main() async {
|
|||||||
navigationModule();
|
navigationModule();
|
||||||
configureAppRouter();
|
configureAppRouter();
|
||||||
themePackages();
|
themePackages();
|
||||||
|
await dotenv.load(fileName: '.env');
|
||||||
|
|
||||||
|
await configureDependencies(QuestiaEnvConfig(), log: kDebugMode);
|
||||||
runApp(const ProviderScope(child: PlatformApp()));
|
runApp(const ProviderScope(child: PlatformApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +29,8 @@ class PlatformApp extends ConsumerWidget {
|
|||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: 'SaveFamily',
|
title: 'SaveFamily',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
fontFamily: 'Stolzl',
|
||||||
|
package: 'fonts',
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF329E95)),
|
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF329E95)),
|
||||||
),
|
),
|
||||||
routerConfig: appRouter,
|
routerConfig: appRouter,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ late final GoRouter appRouter;
|
|||||||
void configureAppRouter() {
|
void configureAppRouter() {
|
||||||
appRouter = GoRouter(
|
appRouter = GoRouter(
|
||||||
navigatorKey: rootNavigatorKey,
|
navigatorKey: rootNavigatorKey,
|
||||||
initialLocation: AppRoutes.login,
|
initialLocation: AppRoutes.linkPhone,
|
||||||
debugLogDiagnostics: true,
|
debugLogDiagnostics: true,
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -23,6 +23,11 @@ void configureAppRouter() {
|
|||||||
name: 'login',
|
name: 'login',
|
||||||
pageBuilder: LoginBuilder().buildPage,
|
pageBuilder: LoginBuilder().buildPage,
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.signup,
|
||||||
|
name: 'signup',
|
||||||
|
pageBuilder: SignupBuilder().buildPage,
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: AppRoutes.onboarding,
|
path: AppRoutes.onboarding,
|
||||||
name: 'onboarding',
|
name: 'onboarding',
|
||||||
@@ -43,7 +48,11 @@ void configureAppRouter() {
|
|||||||
name: 'recover_password',
|
name: 'recover_password',
|
||||||
pageBuilder: RecoverPasswordBuilder().buildPage,
|
pageBuilder: RecoverPasswordBuilder().buildPage,
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.deviceSignup,
|
||||||
|
name: 'device_signup',
|
||||||
|
pageBuilder: DeviceSignupBuilder().buildPage,
|
||||||
|
),
|
||||||
StatefulShellRoute.indexedStack(
|
StatefulShellRoute.indexedStack(
|
||||||
builder: (context, state, navShell) {
|
builder: (context, state, navShell) {
|
||||||
return DashboardBuilder().build(context, navShell);
|
return DashboardBuilder().build(context, navShell);
|
||||||
|
|||||||
@@ -168,6 +168,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
country_code_picker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: country_code_picker
|
||||||
|
sha256: f0411f4833b6f98e8b7215f4fa3813bcc88e50f13925f70a170dbd36e3e447f5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
coverage:
|
coverage:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -214,6 +222,30 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
diacritic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: diacritic
|
||||||
|
sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
dio:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -259,6 +291,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_dotenv:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_dotenv
|
||||||
|
sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -284,10 +324,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
|
sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.3"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -298,6 +338,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fonts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../packages/fonts"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -401,6 +448,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.2"
|
version: "0.20.2"
|
||||||
|
intl_phone_field_v2:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: intl_phone_field_v2
|
||||||
|
sha256: b1e5077e31cc8705639a69b2e0410a8ecc858c3e518726d99b378b6c35adfefb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.5"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -590,6 +645,13 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
sf_infrastructure:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../packages/sf_infrastructure"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -874,4 +936,4 @@ packages:
|
|||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.2 <4.0.0"
|
dart: ">=3.9.2 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
@@ -54,13 +54,18 @@ dependencies:
|
|||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
|
|
||||||
#dependencies go here
|
#dependencies go here
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter_svg: ^2.2.1
|
flutter_svg: ^2.2.1
|
||||||
go_router_builder: ^4.1.1
|
go_router_builder: ^4.1.1
|
||||||
build_runner: ^2.7.1
|
build_runner: ^2.7.1
|
||||||
|
flutter_dotenv: ^6.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -87,7 +92,7 @@ flutter:
|
|||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/images/ui/
|
- assets/images/ui/
|
||||||
|
- .env
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,home,navigation,notifications,profile,sf_shared,utils,sf_localizations
|
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,home,navigation,notifications,profile,sf_shared,utils,sf_localizations,fonts,sf_infrastructure
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
auth:
|
auth:
|
||||||
path: ../../modules/auth
|
path: ../../modules/auth
|
||||||
@@ -6,6 +6,8 @@ dependency_overrides:
|
|||||||
path: ../../modules/dashboard_shell
|
path: ../../modules/dashboard_shell
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
home:
|
home:
|
||||||
path: ../../modules/home
|
path: ../../modules/home
|
||||||
navigation:
|
navigation:
|
||||||
@@ -14,6 +16,8 @@ dependency_overrides:
|
|||||||
path: ../../modules/notifications
|
path: ../../modules/notifications
|
||||||
profile:
|
profile:
|
||||||
path: ../../modules/profile
|
path: ../../modules/profile
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
sf_shared:
|
sf_shared:
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
//await tester.pumpWidget(const PaymentsApp(di: di));
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
export 'src/device_sign_up/link_watch/create_profile_screen.dart';
|
export 'src/features/device_sign_up/link_watch/create_profile_screen.dart';
|
||||||
export 'src/onboarding/onboarding_builder.dart';
|
export 'src/features/onboarding/onboarding_builder.dart';
|
||||||
export 'src/login/link_phone_builder.dart';
|
export 'src/features/link_phone/link_phone_builder.dart';
|
||||||
export 'src/login/phone_code_builder.dart';
|
export 'src/features/login/phone_code_builder.dart';
|
||||||
export 'src/login/login_builder.dart';
|
export 'src/features/login/login_builder.dart';
|
||||||
export 'src/recover_password/recover_password_builder.dart';
|
export 'src/features/recover_password/recover_password_builder.dart';
|
||||||
|
export 'src/features/device_sign_up/device_signup_builder.dart';
|
||||||
|
export 'src/features/sign_up/signup_builder.dart';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
abstract class AuthRemoteDatasource {
|
||||||
|
Future<void> requestPhoneCode({required String phone});
|
||||||
|
|
||||||
|
Future<void> verifyPhoneCode({required String phone, required String code});
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
|
||||||
|
import 'auth_remote_datasource.dart';
|
||||||
|
|
||||||
|
class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||||
|
AuthRemoteDatasourceImpl(this._repository);
|
||||||
|
|
||||||
|
final QuestiaRepository _repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> requestPhoneCode({required String phone}) async {
|
||||||
|
try {
|
||||||
|
await _repository.post<void>(
|
||||||
|
'/auth/link-phone/request-code',
|
||||||
|
body: <String, dynamic>{'phone': phone},
|
||||||
|
);
|
||||||
|
} on DioException catch (error) {
|
||||||
|
throw _mapDioError(error, defaultMessage: 'Error al solicitar el código');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> verifyPhoneCode({
|
||||||
|
required String phone,
|
||||||
|
required String code,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _repository.post<void>(
|
||||||
|
'/auth/link-phone/verify-code',
|
||||||
|
body: <String, dynamic>{'phone': phone, 'code': code},
|
||||||
|
);
|
||||||
|
} on DioException catch (error) {
|
||||||
|
throw _mapDioError(error, defaultMessage: 'Error al verificar el código');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception _mapDioError(DioException error, {required String defaultMessage}) {
|
||||||
|
final responseData = error.response?.data;
|
||||||
|
String message = defaultMessage;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
final serverMessage = responseData['message'];
|
||||||
|
if (serverMessage is String && serverMessage.isNotEmpty) {
|
||||||
|
message = serverMessage;
|
||||||
|
}
|
||||||
|
} else if (error.message != null && error.message!.isNotEmpty) {
|
||||||
|
message = error.message!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart';
|
||||||
|
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||||
|
|
||||||
|
class AuthRepositoryImpl implements AuthRepository {
|
||||||
|
const AuthRepositoryImpl(this._remote);
|
||||||
|
|
||||||
|
final AuthRemoteDatasource _remote;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> requestPhoneCode({required String phone}) {
|
||||||
|
return _remote.requestPhoneCode(phone: phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> verifyPhoneCode({required String phone, required String code}) {
|
||||||
|
return _remote.verifyPhoneCode(phone: phone, code: code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
abstract class AuthRepository {
|
||||||
|
Future<void> requestPhoneCode({required String phone});
|
||||||
|
|
||||||
|
Future<void> verifyPhoneCode({required String phone, required String code});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:auth/src/core/data/datasource/auth_remote_datasource.dart';
|
||||||
|
import 'package:auth/src/core/data/datasource/auth_remote_datasource_impl.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
|
||||||
|
final authRemoteDatasourceProvider = Provider<AuthRemoteDatasource>((ref) {
|
||||||
|
final questiaRepository = getIt<QuestiaRepository>();
|
||||||
|
return AuthRemoteDatasourceImpl(questiaRepository);
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:auth/src/core/data/repositories/auth_repository_impl.dart';
|
||||||
|
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||||
|
import 'package:auth/src/core/providers/auth_remote_datasource_provider.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||||
|
final remote = ref.read(authRemoteDatasourceProvider);
|
||||||
|
return AuthRepositoryImpl(remote);
|
||||||
|
});
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import 'package:auth/src/device_sign_up/link_watch/create_profile_screen.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AddKidScreen extends StatelessWidget {
|
|
||||||
const AddKidScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Column(
|
|
||||||
spacing: 15,
|
|
||||||
children: [
|
|
||||||
Spacer(flex: 6),
|
|
||||||
Text("Añade a tu peque"),
|
|
||||||
Text(
|
|
||||||
"Controla su gasto a la vez que aprende hábitos financieros responsables",
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 30, horizontal: 50),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Column(children: [Text("1"), Text("2"), Text("3")]),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text("Crea su perfil"),
|
|
||||||
Text("Vincula su correa y su reloj"),
|
|
||||||
Text("Carga su hucha"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text("¡Y todo listo para que tenga su dinero!"),
|
|
||||||
Text("Recuerda que necesitas tener un Plan SaveFamily"),
|
|
||||||
Text(
|
|
||||||
"Si aún no lo tienes, puedes conseguirlo a través de nuestra web",
|
|
||||||
),
|
|
||||||
Spacer(flex: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => CreateProfileScreen()),
|
|
||||||
),
|
|
||||||
child: Text("¡Empezar!"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:auth/src/device_sign_up/add_kid_screen.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class AccountCreatedKidScreen extends ConsumerWidget {
|
|
||||||
const AccountCreatedKidScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
final model = "SaveWatch Plus 2";
|
|
||||||
final id = "1106652524";
|
|
||||||
final fullName = "Carlos Pérez Cruz";
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
Spacer(flex: 2),
|
|
||||||
Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Cuenta creada",
|
|
||||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
text: "Has creado la cuenta para:\n",
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: fullName,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text("Reloj: $model"),
|
|
||||||
Text("ID del reloj: $id"),
|
|
||||||
Text(
|
|
||||||
"Ya puedes darle su primera paga paa que empiece a disfrutarla en su reloj",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Spacer(flex: 6),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(20),
|
|
||||||
topLeft: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: () => {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => AddKidScreen()),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
child: Text("Dale su primera paga"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text("Añadir otro peque"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
|
|
||||||
class CreateProfileScreen extends ConsumerWidget {
|
|
||||||
const CreateProfileScreen({super.key});
|
|
||||||
|
|
||||||
final int currentStep = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Stepper(
|
|
||||||
type: StepperType.horizontal,
|
|
||||||
currentStep: currentStep,
|
|
||||||
onStepCancel: () => currentStep == 0,
|
|
||||||
// ? null
|
|
||||||
// :
|
|
||||||
// setState(() {
|
|
||||||
// currentStep -= 1;
|
|
||||||
// }),
|
|
||||||
controlsBuilder:
|
|
||||||
(BuildContext context, ControlsDetails controls) {
|
|
||||||
return FilledButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
|
||||||
theme.getColorFor(ThemeCode.buttonPrimary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: controls.onStepContinue,
|
|
||||||
child: const Text('Continuar'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
steps: [
|
|
||||||
Step(
|
|
||||||
state: currentStep > 0
|
|
||||||
? StepState.complete
|
|
||||||
: StepState.indexed,
|
|
||||||
isActive: currentStep >= 0,
|
|
||||||
stepStyle: currentStep >= 0
|
|
||||||
? StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.transparent,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 5),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: Text(""),
|
|
||||||
content: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Crea su perfil",
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Necesitamos estos datos para crear su cuenta y gestionar sus pagas y gastos",
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Comienza con un peque; luego podrás agregar más",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Nombre",
|
|
||||||
hintText: "Nombre",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Apellidos",
|
|
||||||
hintText: "Apellidos",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: Text("Fecha de nacimiento"),
|
|
||||||
hintText: "DD",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "MM",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "AAAA",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Dirección completa",
|
|
||||||
hintText: "Nombre de la calle",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text(
|
|
||||||
"Cambiar dirección",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
state: currentStep > 1
|
|
||||||
? StepState.complete
|
|
||||||
: StepState.indexed,
|
|
||||||
isActive: currentStep >= 1,
|
|
||||||
stepStyle: currentStep >= 1
|
|
||||||
? StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.transparent,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 5),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: Text(""),
|
|
||||||
content: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Vincula su correa y su reloj",
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SvgPicture.asset("assets/images/ui/formulario.svg"),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text("1"),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text("Escanea la correa"),
|
|
||||||
Text("El peque podrá realizar pagos"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text("2"),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text("Escanea el reloj"),
|
|
||||||
Text("Visualizarás los gastos que se hagan"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
state: currentStep > 2
|
|
||||||
? StepState.complete
|
|
||||||
: StepState.indexed,
|
|
||||||
isActive: currentStep >= 2,
|
|
||||||
stepStyle: currentStep >= 2
|
|
||||||
? StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.transparent,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 5),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: Text(""),
|
|
||||||
content: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"¡Dale su primera paga!",
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Enséñales a gestionar su dinero recargando su reloj",
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Cantidad de dinero de la paga",
|
|
||||||
hintText: "0€",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text("Cantidad mínima: 10€"),
|
|
||||||
Text(
|
|
||||||
"Por seguridad sólo se puede disponer de un máximo de 150€ por wallet",
|
|
||||||
),
|
|
||||||
Text("Método de ingreso"),
|
|
||||||
Row(
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
"assets/images/ui/visa.svg",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
"assets/images/ui/paypal.svg",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(
|
|
||||||
"assets/images/ui/banco.svg",
|
|
||||||
),
|
|
||||||
Text("Transferencia"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.lock_outline),
|
|
||||||
Text(
|
|
||||||
"EL pago en esta app es seguro y cumple la normativa europea. Sólo se usará el dinero que decidas para las huchas de tus hijos.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class AddKidScreen extends ConsumerWidget {
|
||||||
|
final nextStep;
|
||||||
|
|
||||||
|
const AddKidScreen({super.key, required this.nextStep});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Container(
|
||||||
|
margin: EdgeInsets.all(30),
|
||||||
|
child: Column(
|
||||||
|
spacing: 15,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 6),
|
||||||
|
Text("Añade a tu peque", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
|
||||||
|
Text(
|
||||||
|
"Controla su gasto a la vez que aprende hábitos financieros responsables",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 30, horizontal: 50),
|
||||||
|
child: Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: List<Widget>.generate(3, (int index) =>
|
||||||
|
Container(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
child: Center(child: Text(
|
||||||
|
(index + 1).toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Divider(color: Colors.red, thickness: 4,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Text("Crea su perfil"),
|
||||||
|
Text("Vincula su correa y su reloj"),
|
||||||
|
Text("Carga su hucha"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"¡Y todo listo para que tenga su dinero!",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Recuerda que necesitas tener un Plan SaveFamily",
|
||||||
|
textAlign: TextAlign.center
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Si aún no lo tienes, puedes conseguirlo a través de nuestra web",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Spacer(flex: 8),
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: nextStep,
|
||||||
|
text: "¡Empezar!",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class ContactScreen extends StatelessWidget {
|
class ContactScreen extends ConsumerWidget {
|
||||||
const ContactScreen({super.key});
|
const ContactScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
body: Container(
|
body: Container(
|
||||||
margin: EdgeInsets.all(30),
|
margin: EdgeInsets.all(30),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -36,32 +41,22 @@ class ContactScreen extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: CustomTextField(
|
||||||
decoration: InputDecoration(
|
label: "Nombre",
|
||||||
labelText: "Nombre",
|
hint: "Nombre y apellidos",
|
||||||
hintText: "Nombre y apellidos",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: CustomTextField(
|
||||||
decoration: InputDecoration(
|
label: "Correo electrónico",
|
||||||
labelText: "Correo electrónico",
|
hint: "Correo electrónico",
|
||||||
hintText: "Correo electrónico",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: CustomTextField(
|
||||||
minLines: 3,
|
lines: 3,
|
||||||
maxLines: 3,
|
label: "Asunto del mensaje",
|
||||||
decoration: InputDecoration(
|
hint: "Escribe tu mensaje",
|
||||||
labelText: "Asunto del mensaje",
|
|
||||||
hintText: "Escribe tu mensaje",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:auth/src/features/device_sign_up/device_signup_screen.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
|
class DeviceSignupBuilder {
|
||||||
|
const DeviceSignupBuilder();
|
||||||
|
|
||||||
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
|
||||||
|
|
||||||
|
return MaterialPage<void>(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: DeviceSignupScreen(navigationContract: navigationContract),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:auth/auth.dart';
|
||||||
|
import 'package:auth/src/features/device_sign_up/add_kid_screen.dart';
|
||||||
|
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_screen.dart';
|
||||||
|
import 'package:auth/src/features/device_sign_up/link_watch/link_watch_previous_screen.dart';
|
||||||
|
import 'package:auth/src/features/sign_up/account_created_screen.dart';
|
||||||
|
import 'package:auth/src/widgets/layouts/form_step_layout.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
|
class DeviceSignupScreen extends ConsumerStatefulWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const DeviceSignupScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<DeviceSignupScreen> createState() =>
|
||||||
|
DeviceSignupScreenState(navigationContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen> {
|
||||||
|
late int currentStep;
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
DeviceSignupScreenState(this.navigationContract);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
currentStep = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return getSteps()[currentStep];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> getSteps() {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
final continueBtn = Container(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
text: "Continuar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
AddKidScreen(
|
||||||
|
nextStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Crea su perfil",
|
||||||
|
subtitle:
|
||||||
|
"Necesitamos estos datos para crear su cuenta y gestionar sus pagos y gastos",
|
||||||
|
currentStep: 1,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [CreateProfileScreen()],
|
||||||
|
footer: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
child: continueBtn,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
nextStep: () => {},
|
||||||
|
previousStep: () => {},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Vincula su correa y su reloj",
|
||||||
|
currentStep: 2,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [LinkWatchPreviousScreen()],
|
||||||
|
footer: [continueBtn],
|
||||||
|
nextStep: () => {},
|
||||||
|
previousStep: () => {},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Vincula su correa\ny su reloj",
|
||||||
|
currentStep: 2,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [LinkWatchScreen(step: 1)],
|
||||||
|
footer: [continueBtn],
|
||||||
|
nextStep: () => {},
|
||||||
|
previousStep: () => {},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Vincula su correa\ny su reloj",
|
||||||
|
currentStep: 2,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [LinkWatchScreen(step: 2)],
|
||||||
|
footer: [continueBtn],
|
||||||
|
nextStep: () => {},
|
||||||
|
previousStep: () => {},
|
||||||
|
),
|
||||||
|
AccountCreatedScreen(
|
||||||
|
navigationContract: navigationContract,
|
||||||
|
kidAccount: true,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class CreateProfileScreen extends ConsumerWidget {
|
||||||
|
const CreateProfileScreen({super.key});
|
||||||
|
|
||||||
|
final bool firstTime = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comienza con un peque; luego podrás agregar más",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
label: "Nombre",
|
||||||
|
hint: "Nombre",
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
label: "Apellidos",
|
||||||
|
hint: "Apellidos",
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
"Fecha de nacimiento",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomTextField(
|
||||||
|
numeric: true,
|
||||||
|
hint: "DD",
|
||||||
|
length: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomTextField(
|
||||||
|
numeric: true,
|
||||||
|
hint: "MM",
|
||||||
|
length: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomTextField(
|
||||||
|
numeric: true,
|
||||||
|
hint: "AAAA",
|
||||||
|
length: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
label: "Dirección completa",
|
||||||
|
hint: "Nombre de la calle",
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: CustomTextButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Cambiar dirección",
|
||||||
|
size: 18,
|
||||||
|
weight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
|
class LinkWatchPreviousScreen extends ConsumerWidget{
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset("assets/images/ui/formulario.svg"),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary)
|
||||||
|
),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: Center(child: Text("1", style: TextStyle(fontSize: 24))),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text("Escanea la correa", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)),
|
||||||
|
Text("El peque podrá realizar pagos"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary)
|
||||||
|
),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: Center(child: Text("2", style: TextStyle(fontSize: 24))),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text("Escanea el reloj", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)),
|
||||||
|
Text("Visualizarás los gastos que se hagan"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
class LinkWatchScreen extends ConsumerStatefulWidget{
|
||||||
|
final int step;
|
||||||
|
|
||||||
|
const LinkWatchScreen({super.key, required this.step});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<LinkWatchScreen> createState() => LinkWatchScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkWatchScreenState extends ConsumerState<LinkWatchScreen>{
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Divider(
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
thickness: 4,
|
||||||
|
indent: 93,
|
||||||
|
endIndent: 93,
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
if (widget.step==1)Divider(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
|
thickness: 4,
|
||||||
|
indent: 186,
|
||||||
|
endIndent: 93,
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 69),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: Center(child: Text(
|
||||||
|
"1",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
fontSize: 24
|
||||||
|
)
|
||||||
|
))
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Container(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
color: theme.getColorFor(widget.step==1 ? ThemeCode.backgroundSecondary : ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: Center(child: Text(
|
||||||
|
"2",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(widget.step==1 ? ThemeCode.textPrimary : ThemeCode.backgroundSecondary),
|
||||||
|
fontSize: 24
|
||||||
|
)
|
||||||
|
))
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(children: [
|
||||||
|
Spacer(),
|
||||||
|
Text("Escanea la correa"),
|
||||||
|
Spacer(),
|
||||||
|
Text("Escanea el reloj"),
|
||||||
|
Spacer(),
|
||||||
|
]),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(40),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16))
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset("assets/images/ui/qr.svg")
|
||||||
|
),
|
||||||
|
if (widget.step == 2)Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text("O inserta el código del reloj"),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
hint: "XXXXXXXXXX",
|
||||||
|
)),
|
||||||
|
Expanded(child: PrimaryButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
text: "Continuar con código",
|
||||||
|
size: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary)
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text("Si no consigues vincular su correa o reloj"),
|
||||||
|
CustomTextButton(onPressed: ()=>{}, text: "Contáctanos", weight: FontWeight.w500, size: 18)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
abstract class LinkPhoneUseCase {
|
||||||
|
Future<void> requestCode({required String phone});
|
||||||
|
|
||||||
|
Future<void> verifyCode({required String phone, required String code});
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:auth/src/core/domain/repositories/auth_repository.dart';
|
||||||
|
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
|
||||||
|
|
||||||
|
class LinkPhoneUseCaseImpl implements LinkPhoneUseCase {
|
||||||
|
LinkPhoneUseCaseImpl(this._repository);
|
||||||
|
|
||||||
|
final AuthRepository _repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> requestCode({required String phone}) {
|
||||||
|
return _repository.requestPhoneCode(phone: phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> verifyCode({required String phone, required String code}) {
|
||||||
|
return _repository.verifyPhoneCode(phone: phone, code: code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:auth/src/login/presentation/link_phone_screen.dart';
|
import 'package:auth/src/features/link_phone/presentation/link_phone_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import 'package:auth/src/features/link_phone/presentation/link_phone_view_model.dart';
|
||||||
|
import 'package:design_system/src/dropdowns/country_prefix_picker.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
class LinkPhoneScreen extends ConsumerWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const LinkPhoneScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
final viewModel = ref.read(linkPhoneViewModelProvider.notifier);
|
||||||
|
final viewState = ref.watch(linkPhoneViewModelProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.linkPhoneTitle),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.linkPhoneSubtitle),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.mobilePhone),
|
||||||
|
style: const TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
CountryPrefixPicker(
|
||||||
|
initialCountryCode: viewState.dialCode,
|
||||||
|
onChanged: (country) {
|
||||||
|
viewModel.updateDialCode(
|
||||||
|
country.dialCode ?? viewState.dialCode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomTextField(
|
||||||
|
controller: viewModel.phoneNumberController,
|
||||||
|
hint: context.translate(I18n.phoneNumber),
|
||||||
|
numeric: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
if (viewState.errorMessage.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
viewState.errorMessage,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color.fromRGBO(239, 17, 17, 1),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await viewModel.requestCode();
|
||||||
|
final updatedState = ref.read(linkPhoneViewModelProvider);
|
||||||
|
if (updatedState.errorMessage.isEmpty) {
|
||||||
|
navigationContract.pushTo(AppRoutes.phoneCode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: context.translate(I18n.next),
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:auth/src/features/link_phone/presentation/providers/link_phone_provider.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
|
||||||
|
import 'package:auth/src/features/link_phone/presentation/link_phone_view_state.dart';
|
||||||
|
|
||||||
|
final linkPhoneViewModelProvider =
|
||||||
|
NotifierProvider.autoDispose<LinkPhoneViewModel, LinkPhoneViewState>(
|
||||||
|
LinkPhoneViewModel.new,
|
||||||
|
);
|
||||||
|
|
||||||
|
class LinkPhoneViewModel extends Notifier<LinkPhoneViewState> {
|
||||||
|
late final LinkPhoneUseCase _linkPhoneUseCase;
|
||||||
|
late final TextEditingController phoneNumberController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
LinkPhoneViewState build() {
|
||||||
|
_linkPhoneUseCase = ref.read(linkPhoneUseCaseProvider);
|
||||||
|
|
||||||
|
phoneNumberController = TextEditingController();
|
||||||
|
phoneNumberController.addListener(_onPhoneNumberChanged);
|
||||||
|
|
||||||
|
ref.onDispose(disposeControllers);
|
||||||
|
|
||||||
|
return const LinkPhoneViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPhoneNumberChanged() {
|
||||||
|
final raw = phoneNumberController.text;
|
||||||
|
state = state.copyWith(phoneNumber: raw, errorMessage: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDialCode(String dialCode) {
|
||||||
|
state = state.copyWith(dialCode: dialCode, errorMessage: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> requestCode() async {
|
||||||
|
final trimmedNumber = state.phoneNumber.trim();
|
||||||
|
|
||||||
|
if (trimmedNumber.isEmpty) {
|
||||||
|
state = state.copyWith(errorMessage: 'El teléfono no puede estar vacío');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fullPhone = '${state.dialCode}$trimmedNumber';
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
isLoading: true,
|
||||||
|
errorMessage: '',
|
||||||
|
codeRequested: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _linkPhoneUseCase.requestCode(phone: fullPhone);
|
||||||
|
if (!ref.mounted) return;
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: '',
|
||||||
|
codeRequested: true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!ref.mounted) return;
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
codeRequested: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disposeControllers() {
|
||||||
|
phoneNumberController.removeListener(_onPhoneNumberChanged);
|
||||||
|
phoneNumberController.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'link_phone_view_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class LinkPhoneViewState with _$LinkPhoneViewState {
|
||||||
|
const factory LinkPhoneViewState({
|
||||||
|
@Default('') String phoneNumber,
|
||||||
|
@Default('+34') String dialCode,
|
||||||
|
@Default('') String errorMessage,
|
||||||
|
@Default(false) bool isLoading,
|
||||||
|
@Default(false) bool codeRequested,
|
||||||
|
}) = _LinkPhoneViewState;
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'link_phone_view_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$LinkPhoneViewState {
|
||||||
|
|
||||||
|
String get phoneNumber; String get dialCode; String get errorMessage; bool get isLoading; bool get codeRequested;
|
||||||
|
/// Create a copy of LinkPhoneViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$LinkPhoneViewStateCopyWith<LinkPhoneViewState> get copyWith => _$LinkPhoneViewStateCopyWithImpl<LinkPhoneViewState>(this as LinkPhoneViewState, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $LinkPhoneViewStateCopyWith<$Res> {
|
||||||
|
factory $LinkPhoneViewStateCopyWith(LinkPhoneViewState value, $Res Function(LinkPhoneViewState) _then) = _$LinkPhoneViewStateCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$LinkPhoneViewStateCopyWithImpl<$Res>
|
||||||
|
implements $LinkPhoneViewStateCopyWith<$Res> {
|
||||||
|
_$LinkPhoneViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final LinkPhoneViewState _self;
|
||||||
|
final $Res Function(LinkPhoneViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of LinkPhoneViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [LinkPhoneViewState].
|
||||||
|
extension LinkPhoneViewStatePatterns on LinkPhoneViewState {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LinkPhoneViewState value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LinkPhoneViewState value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LinkPhoneViewState value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState() when $default != null:
|
||||||
|
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState():
|
||||||
|
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _LinkPhoneViewState() when $default != null:
|
||||||
|
return $default(_that.phoneNumber,_that.dialCode,_that.errorMessage,_that.isLoading,_that.codeRequested);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _LinkPhoneViewState implements LinkPhoneViewState {
|
||||||
|
const _LinkPhoneViewState({this.phoneNumber = '', this.dialCode = '+34', this.errorMessage = '', this.isLoading = false, this.codeRequested = false});
|
||||||
|
|
||||||
|
|
||||||
|
@override@JsonKey() final String phoneNumber;
|
||||||
|
@override@JsonKey() final String dialCode;
|
||||||
|
@override@JsonKey() final String errorMessage;
|
||||||
|
@override@JsonKey() final bool isLoading;
|
||||||
|
@override@JsonKey() final bool codeRequested;
|
||||||
|
|
||||||
|
/// Create a copy of LinkPhoneViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$LinkPhoneViewStateCopyWith<_LinkPhoneViewState> get copyWith => __$LinkPhoneViewStateCopyWithImpl<_LinkPhoneViewState>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LinkPhoneViewState&&(identical(other.phoneNumber, phoneNumber) || other.phoneNumber == phoneNumber)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.codeRequested, codeRequested) || other.codeRequested == codeRequested));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,phoneNumber,dialCode,errorMessage,isLoading,codeRequested);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LinkPhoneViewState(phoneNumber: $phoneNumber, dialCode: $dialCode, errorMessage: $errorMessage, isLoading: $isLoading, codeRequested: $codeRequested)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$LinkPhoneViewStateCopyWith<$Res> implements $LinkPhoneViewStateCopyWith<$Res> {
|
||||||
|
factory _$LinkPhoneViewStateCopyWith(_LinkPhoneViewState value, $Res Function(_LinkPhoneViewState) _then) = __$LinkPhoneViewStateCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String phoneNumber, String dialCode, String errorMessage, bool isLoading, bool codeRequested
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$LinkPhoneViewStateCopyWithImpl<$Res>
|
||||||
|
implements _$LinkPhoneViewStateCopyWith<$Res> {
|
||||||
|
__$LinkPhoneViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _LinkPhoneViewState _self;
|
||||||
|
final $Res Function(_LinkPhoneViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of LinkPhoneViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? phoneNumber = null,Object? dialCode = null,Object? errorMessage = null,Object? isLoading = null,Object? codeRequested = null,}) {
|
||||||
|
return _then(_LinkPhoneViewState(
|
||||||
|
phoneNumber: null == phoneNumber ? _self.phoneNumber : phoneNumber // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,codeRequested: null == codeRequested ? _self.codeRequested : codeRequested // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:auth/src/core/providers/auth_repository_provider.dart';
|
||||||
|
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case.dart';
|
||||||
|
import 'package:auth/src/features/link_phone/domain/use_cases/link_phone_use_case_impl.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
final linkPhoneUseCaseProvider = Provider.autoDispose<LinkPhoneUseCase>((ref) {
|
||||||
|
final authRepository = ref.read(authRepositoryProvider);
|
||||||
|
return LinkPhoneUseCaseImpl(authRepository);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:auth/src/login/presentation/login_screen.dart';
|
import 'package:auth/src/features/login/presentation/login_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:auth/src/login/presentation/phone_code_screen.dart';
|
import 'package:auth/src/features/login/presentation/phone_code_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import 'package:auth/src/features/login/presentation/loading_google_screen.dart';
|
||||||
|
import 'package:auth/src/features/sign_up/signup_screen.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
class LoginScreen extends ConsumerStatefulWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const LoginScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<ConsumerStatefulWidget> createState() => _LoginScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||||
|
bool passwordVisible = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
bool passwordVisible = true;
|
||||||
|
|
||||||
|
final content = [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// context.translate(I18n.example)
|
||||||
|
"¡Te damos la bienvenida!",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
hint: "Nombre de usuario",
|
||||||
|
label: "Nombre de usuario",
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
showPassword: passwordVisible,
|
||||||
|
label: "Contraseña",
|
||||||
|
hint: "********",
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: CustomTextButton(
|
||||||
|
text: "¿Has olvidado la contraseña?",
|
||||||
|
onPressed: () => widget.navigationContract.pushTo(
|
||||||
|
AppRoutes.recoverPassword,
|
||||||
|
),
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () =>
|
||||||
|
widget.navigationContract.goTo(AppRoutes.dashboardHome),
|
||||||
|
text: "Iniciar sesión",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 24),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Divider(endIndent: 74, indent: 74),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
child: Text("o continúa con"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
SecondaryButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => LoadingGoogleScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
radius: 16,
|
||||||
|
padding: 44,
|
||||||
|
text: "Google",
|
||||||
|
label: "Google",
|
||||||
|
),
|
||||||
|
SecondaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
radius: 16,
|
||||||
|
padding: 44,
|
||||||
|
icon: Icons.apple,
|
||||||
|
label: "Apple",
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"¿No tienes cuenta?",
|
||||||
|
style: TextStyle(fontSize: 18, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
widget.navigationContract.goTo(AppRoutes.signup),
|
||||||
|
child: Text(
|
||||||
|
"Crear una ahora",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.all(30),
|
||||||
|
child: ListView.separated(
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return content[index];
|
||||||
|
},
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return Divider(color: Colors.transparent, height: 48);
|
||||||
|
},
|
||||||
|
itemCount: content.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class PhoneCodeScreen extends ConsumerWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
PhoneCodeScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
final focusNodes = List<FocusNode>.generate(6, (int i) {
|
||||||
|
return FocusNode();
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Container(
|
||||||
|
margin: EdgeInsets.all(30),
|
||||||
|
child: Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
spacing: 48,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 8),
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Conéctate",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: "Hemos enviado el código al ",
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
// text: widget.phone,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text("Introduce el código aquí"),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: List<Widget>.generate(6, (int i) {
|
||||||
|
return Expanded(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes[i],
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "0",
|
||||||
|
counterText: "",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLength: 1,
|
||||||
|
onChanged: (String value) => {
|
||||||
|
value != ""
|
||||||
|
? focusNodes[i + 1].requestFocus()
|
||||||
|
: focusNodes[i - 1].requestFocus(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () => {
|
||||||
|
navigationContract.pushTo(AppRoutes.login),
|
||||||
|
},
|
||||||
|
text: "Entrar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"¿No lo has recibido?",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Volver a intentarlo",
|
||||||
|
size: 18,
|
||||||
|
weight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(flex: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
class OnboardingPage {
|
||||||
|
final String image;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
|
||||||
|
const OnboardingPage({
|
||||||
|
required this.image,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const List<OnboardingPage> onboardingPages = <OnboardingPage>[
|
||||||
|
OnboardingPage(
|
||||||
|
image: 'assets/images/ui/bienvenida_paso1.svg',
|
||||||
|
title: I18n.onboardingTitle1,
|
||||||
|
subtitle: I18n.onboardingSubtitle1,
|
||||||
|
),
|
||||||
|
OnboardingPage(
|
||||||
|
image: 'assets/images/ui/bienvenida_paso2.svg',
|
||||||
|
title: I18n.onboardingTitle2,
|
||||||
|
subtitle: I18n.onboardingSubtitle2,
|
||||||
|
),
|
||||||
|
OnboardingPage(
|
||||||
|
image: 'assets/images/ui/bienvenida_paso3.svg',
|
||||||
|
title: I18n.onboardingTitle3,
|
||||||
|
subtitle: I18n.onboardingSubtitle3,
|
||||||
|
),
|
||||||
|
];
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:auth/src/onboarding/presentation/welcome_screen.dart';
|
import 'package:auth/src/features/onboarding/presentation/onboarding_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -12,7 +12,7 @@ class OnboardingBuilder {
|
|||||||
|
|
||||||
return MaterialPage<void>(
|
return MaterialPage<void>(
|
||||||
key: state.pageKey,
|
key: state.pageKey,
|
||||||
child: WelcomeScreen(navigationContract: navigationContract),
|
child: OnboardingScreen(navigationContract: navigationContract),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import 'package:auth/src/features/onboarding/domain/onboarding_page.dart';
|
||||||
|
import 'package:auth/src/features/onboarding/presentation/onboarding_view_model.dart';
|
||||||
|
import 'package:auth/src/features/onboarding/presentation/widgets/onboarding_content.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
final onboardingPageControllerProvider = Provider.autoDispose<PageController>((
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
final controller = PageController();
|
||||||
|
ref.onDispose(controller.dispose);
|
||||||
|
return controller;
|
||||||
|
});
|
||||||
|
|
||||||
|
class OnboardingScreen extends ConsumerWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const OnboardingScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final state = ref.watch(onBoardingViewModelProvider);
|
||||||
|
final viewModel = ref.read(onBoardingViewModelProvider.notifier);
|
||||||
|
final pageController = ref.watch(onboardingPageControllerProvider);
|
||||||
|
|
||||||
|
final isLast = state.cardIndex >= onboardingPages.length - 1;
|
||||||
|
|
||||||
|
void goToNext() {
|
||||||
|
if (isLast) {
|
||||||
|
navigationContract.goTo(AppRoutes.linkPhone);
|
||||||
|
} else {
|
||||||
|
pageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Color(0xFFF7F7F7),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: pageController,
|
||||||
|
itemCount: onboardingPages.length,
|
||||||
|
onPageChanged: viewModel.onPageChanged,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final page = onboardingPages[index];
|
||||||
|
return OnboardingContent(
|
||||||
|
image: page.image,
|
||||||
|
title: page.title,
|
||||||
|
subtitle: page.subtitle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StepIndicator(
|
||||||
|
current: state.cardIndex + 1,
|
||||||
|
total: onboardingPages.length,
|
||||||
|
color: const Color(0xFF4B4B4B),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
width: double.infinity,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: goToNext,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: isLast
|
||||||
|
? const Color(0xFF329E95)
|
||||||
|
: const Color(0xFF4B4B4B),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
isLast
|
||||||
|
? context.translate(I18n.start)
|
||||||
|
: context.translate(I18n.next),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: Center(
|
||||||
|
child: isLast
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
navigationContract.goTo(AppRoutes.linkPhone),
|
||||||
|
child: Text(
|
||||||
|
context.translate(I18n.skip),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF4B4B4B),
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 36),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:auth/src/features/onboarding/presentation/onboarding_view_state.dart';
|
||||||
|
|
||||||
|
final onBoardingViewModelProvider =
|
||||||
|
NotifierProvider.autoDispose<OnBoardingViewModel, OnboardingViewState>(
|
||||||
|
OnBoardingViewModel.new,
|
||||||
|
);
|
||||||
|
|
||||||
|
class OnBoardingViewModel extends Notifier<OnboardingViewState> {
|
||||||
|
@override
|
||||||
|
OnboardingViewState build() {
|
||||||
|
return const OnboardingViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPageChanged(int index) {
|
||||||
|
state = state.copyWith(cardIndex: index);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'onboarding_view_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class OnboardingViewState with _$OnboardingViewState {
|
||||||
|
const factory OnboardingViewState({
|
||||||
|
@Default(0) int cardIndex,
|
||||||
|
String? error,
|
||||||
|
}) = _OnboardingViewState;
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'onboarding_view_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$OnboardingViewState {
|
||||||
|
|
||||||
|
int get cardIndex; String? get error;
|
||||||
|
/// Create a copy of OnboardingViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$OnboardingViewStateCopyWith<OnboardingViewState> get copyWith => _$OnboardingViewStateCopyWithImpl<OnboardingViewState>(this as OnboardingViewState, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is OnboardingViewState&&(identical(other.cardIndex, cardIndex) || other.cardIndex == cardIndex)&&(identical(other.error, error) || other.error == error));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,cardIndex,error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'OnboardingViewState(cardIndex: $cardIndex, error: $error)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $OnboardingViewStateCopyWith<$Res> {
|
||||||
|
factory $OnboardingViewStateCopyWith(OnboardingViewState value, $Res Function(OnboardingViewState) _then) = _$OnboardingViewStateCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int cardIndex, String? error
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$OnboardingViewStateCopyWithImpl<$Res>
|
||||||
|
implements $OnboardingViewStateCopyWith<$Res> {
|
||||||
|
_$OnboardingViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final OnboardingViewState _self;
|
||||||
|
final $Res Function(OnboardingViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of OnboardingViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? cardIndex = null,Object? error = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
cardIndex: null == cardIndex ? _self.cardIndex : cardIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [OnboardingViewState].
|
||||||
|
extension OnboardingViewStatePatterns on OnboardingViewState {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _OnboardingViewState value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _OnboardingViewState value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _OnboardingViewState value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int cardIndex, String? error)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState() when $default != null:
|
||||||
|
return $default(_that.cardIndex,_that.error);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int cardIndex, String? error) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState():
|
||||||
|
return $default(_that.cardIndex,_that.error);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int cardIndex, String? error)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _OnboardingViewState() when $default != null:
|
||||||
|
return $default(_that.cardIndex,_that.error);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _OnboardingViewState implements OnboardingViewState {
|
||||||
|
const _OnboardingViewState({this.cardIndex = 0, this.error});
|
||||||
|
|
||||||
|
|
||||||
|
@override@JsonKey() final int cardIndex;
|
||||||
|
@override final String? error;
|
||||||
|
|
||||||
|
/// Create a copy of OnboardingViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$OnboardingViewStateCopyWith<_OnboardingViewState> get copyWith => __$OnboardingViewStateCopyWithImpl<_OnboardingViewState>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _OnboardingViewState&&(identical(other.cardIndex, cardIndex) || other.cardIndex == cardIndex)&&(identical(other.error, error) || other.error == error));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,cardIndex,error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'OnboardingViewState(cardIndex: $cardIndex, error: $error)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$OnboardingViewStateCopyWith<$Res> implements $OnboardingViewStateCopyWith<$Res> {
|
||||||
|
factory _$OnboardingViewStateCopyWith(_OnboardingViewState value, $Res Function(_OnboardingViewState) _then) = __$OnboardingViewStateCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int cardIndex, String? error
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$OnboardingViewStateCopyWithImpl<$Res>
|
||||||
|
implements _$OnboardingViewStateCopyWith<$Res> {
|
||||||
|
__$OnboardingViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _OnboardingViewState _self;
|
||||||
|
final $Res Function(_OnboardingViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of OnboardingViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? cardIndex = null,Object? error = freezed,}) {
|
||||||
|
return _then(_OnboardingViewState(
|
||||||
|
cardIndex: null == cardIndex ? _self.cardIndex : cardIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
class OnboardingContent extends StatelessWidget {
|
||||||
|
final String image;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
|
||||||
|
const OnboardingContent({
|
||||||
|
super.key,
|
||||||
|
required this.image,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(flex: 3, child: SvgPicture.asset(image)),
|
||||||
|
const SizedBox(height: 48),
|
||||||
|
Text(
|
||||||
|
context.translate(title),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 30,
|
||||||
|
height: 1.25,
|
||||||
|
letterSpacing: 0,
|
||||||
|
color: Color(0xFF4B4B4B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
context.translate(subtitle),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
height: 1.5,
|
||||||
|
letterSpacing: 0,
|
||||||
|
color: Color(0xFF4B4B4B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
|
class NewPasswordScreen extends ConsumerStatefulWidget {
|
||||||
|
const NewPasswordScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<NewPasswordScreen> createState() => NewPasswordScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||||
|
bool passwordVisible = false;
|
||||||
|
bool equalPasswords = false;
|
||||||
|
String password = '';
|
||||||
|
|
||||||
|
Map<String, bool> securityChecks = {
|
||||||
|
'min': false,
|
||||||
|
'capital': false,
|
||||||
|
'number': false,
|
||||||
|
'special': false,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
passwordVisible = false;
|
||||||
|
equalPasswords = false;
|
||||||
|
password = '';
|
||||||
|
securityChecks = {
|
||||||
|
'min': false,
|
||||||
|
'capital': false,
|
||||||
|
'number': false,
|
||||||
|
'special': false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Container(
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
spacing: 48,
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Recuperar contraseña",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
showPassword: passwordVisible,
|
||||||
|
label: "Nueva contraseña",
|
||||||
|
hint: "********",
|
||||||
|
onChanged: (value) => {
|
||||||
|
setState(() {
|
||||||
|
password = value;
|
||||||
|
securityChecks = checkSecurity(value);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
showPassword: passwordVisible,
|
||||||
|
label: "Repetir contraseña",
|
||||||
|
hint: "********",
|
||||||
|
onChanged: (value) => {
|
||||||
|
setState(() {
|
||||||
|
equalPasswords = password == value;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(securityChecks["min"]!
|
||||||
|
? ThemeCode.buttonPrimary
|
||||||
|
: ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
const Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(securityChecks["capital"]!
|
||||||
|
? ThemeCode.buttonPrimary
|
||||||
|
: ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
const Text("Una mayúscula", style: TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(securityChecks["number"]!
|
||||||
|
? ThemeCode.buttonPrimary
|
||||||
|
: ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
const Text("Un número", style: TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(securityChecks["special"]!
|
||||||
|
? ThemeCode.buttonPrimary
|
||||||
|
: ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
const Text("Un carácter especial", style: TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: const Text(
|
||||||
|
"Teléfono móvil",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
CustomDropdown(
|
||||||
|
value: 0,
|
||||||
|
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
|
||||||
|
onChanged: (value)=> {},
|
||||||
|
width: 80,
|
||||||
|
),
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
hint: "Teléfono",
|
||||||
|
numeric: true
|
||||||
|
))
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: ()=>{navigationContract.goTo(AppRoutes.dashboardHome)},
|
||||||
|
text: "Aceptar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Extraer de la vista
|
||||||
|
Map<String, bool> checkSecurity(String value) {
|
||||||
|
return {
|
||||||
|
'min': value.length >= 8,
|
||||||
|
'capital': RegExp(r'[A-Z]').hasMatch(value),
|
||||||
|
'number': RegExp(r'[0-9]').hasMatch(value),
|
||||||
|
'special': RegExp(r'[^A-Za-z0-9]').hasMatch(value),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import 'package:auth/src/features/recover_password/presentation/sent_screen.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class RestorePasswordScreen extends ConsumerWidget {
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const RestorePasswordScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Container(
|
||||||
|
margin: EdgeInsets.all(30),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
spacing: 48,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 8),
|
||||||
|
Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Recuperar contaseña",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Introduce tu email para enviarte un enlace de recuperación",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 40,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
label: "Correo electrónico",
|
||||||
|
hint: "Correo electrónico",
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
"Teléfono móvil",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
CustomDropdown(
|
||||||
|
value: 0,
|
||||||
|
items: [
|
||||||
|
Icon(Icons.outlined_flag),
|
||||||
|
Icon(Icons.outlined_flag),
|
||||||
|
Icon(Icons.outlined_flag),
|
||||||
|
],
|
||||||
|
onChanged: (value) => {},
|
||||||
|
width: 80,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomTextField(
|
||||||
|
hint: "Teléfono",
|
||||||
|
numeric: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
onPressed: () => {Navigator.pop(context)},
|
||||||
|
text: "Volver",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () => {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => SentScreen(format: "email"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
text: "Enviar",
|
||||||
|
size: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(flex: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import 'package:auth/src/features/recover_password/presentation/new_password_screen.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class SentScreen extends ConsumerWidget {
|
||||||
|
final String format;
|
||||||
|
|
||||||
|
const SentScreen({super.key, required this.format});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: Container(
|
||||||
|
margin: EdgeInsets.all(24),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
spacing: 48,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 8),
|
||||||
|
Text(
|
||||||
|
"Recuperar contraseña",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 30,
|
||||||
|
letterSpacing: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
format == "email"
|
||||||
|
? "Correo enviado correctamente"
|
||||||
|
: "SMS enviado correctamente",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
format == "email"
|
||||||
|
? "Revisa tu email y haz clic en el enlace para crear una nueva contraseña."
|
||||||
|
: "Revisa tu móvil y sigue las instrucciones para crear una nueva contraseña.",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
format == "email"
|
||||||
|
? "Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"."
|
||||||
|
: "Si no recibes el SMS en unos minutos, asegúrate de tener cobertura o pulsa \"Reenviar SMS \".",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: format == "email"
|
||||||
|
? "Reenviar correo"
|
||||||
|
: "Reenviar SMS",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => NewPasswordScreen()),
|
||||||
|
),
|
||||||
|
text: "Continuar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(flex: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:auth/src/recover_password/presentation/restore_password_screen.dart';
|
import 'package:auth/src/features/recover_password/presentation/restore_password_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
import 'package:auth/src/device_sign_up/add_kid_screen.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
class AccountCreatedScreen extends ConsumerWidget {
|
class AccountCreatedScreen extends ConsumerWidget {
|
||||||
const AccountCreatedScreen({super.key});
|
final bool kidAccount;
|
||||||
|
final NavigationContract navigationContract;
|
||||||
|
|
||||||
|
const AccountCreatedScreen({
|
||||||
|
super.key,
|
||||||
|
required this.kidAccount,
|
||||||
|
required this.navigationContract,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -12,6 +19,8 @@ class AccountCreatedScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
final email = "usuario@example.com";
|
final email = "usuario@example.com";
|
||||||
final fullName = "Carlos Pérez Cruz";
|
final fullName = "Carlos Pérez Cruz";
|
||||||
|
final model = "SaveWatch Plus 2";
|
||||||
|
final id = "1106652524";
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -24,7 +33,7 @@ class AccountCreatedScreen extends ConsumerWidget {
|
|||||||
Spacer(flex: 10),
|
Spacer(flex: 10),
|
||||||
Icon(
|
Icon(
|
||||||
Icons.check,
|
Icons.check,
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
size: 50,
|
size: 50,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -32,6 +41,7 @@ class AccountCreatedScreen extends ConsumerWidget {
|
|||||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Has creado la cuenta para:\n",
|
text: "Has creado la cuenta para:\n",
|
||||||
children: [
|
children: [
|
||||||
@@ -42,7 +52,8 @@ class AccountCreatedScreen extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text.rich(
|
if (!kidAccount) Text.rich(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Hemos enviado un email de verificación a:\n",
|
text: "Hemos enviado un email de verificación a:\n",
|
||||||
children: [
|
children: [
|
||||||
@@ -53,17 +64,21 @@ class AccountCreatedScreen extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (kidAccount) Text("Reloj: $model\nID del reloj: $id"),
|
||||||
Text(
|
Text(
|
||||||
"Crea la cuenta de tu peque e ingresa su \nprimera paga para utilizarla con su reloj",
|
"Crea la cuenta de tu peque e ingresa su \nprimera paga para utilizarla con su reloj",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
FilledButton(
|
PrimaryButton(
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.pushReplacement(
|
if (kidAccount){
|
||||||
context,
|
navigationContract.goTo(AppRoutes.dashboardHome)
|
||||||
MaterialPageRoute(builder: (_) => AddKidScreen()),
|
} else {
|
||||||
),
|
navigationContract.pushTo(AppRoutes.deviceSignup)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Text("Continuar"),
|
text: "Continuar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
),
|
),
|
||||||
Spacer(flex: 8),
|
Spacer(flex: 8),
|
||||||
],
|
],
|
||||||
103
modules/auth/lib/src/features/sign_up/signup_address_screen.dart
Normal file
103
modules/auth/lib/src/features/sign_up/signup_address_screen.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class SignupAddressScreen extends ConsumerStatefulWidget {
|
||||||
|
const SignupAddressScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<SignupAddressScreen> createState() => SignupAddressScreenState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignupAddressScreenState extends ConsumerState<SignupAddressScreen>{
|
||||||
|
|
||||||
|
late String country;
|
||||||
|
late int relation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
relation = 0;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.bottomLeft, child: Text(
|
||||||
|
"Fecha de nacimiento",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
//label: "Fecha de nacimiento",
|
||||||
|
hint: "DD",
|
||||||
|
length: 2,
|
||||||
|
numeric: true,
|
||||||
|
)),
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
hint: "MM",
|
||||||
|
length: 2,
|
||||||
|
numeric: true,
|
||||||
|
)),
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
hint: "AAAA",
|
||||||
|
length: 4,
|
||||||
|
numeric: true,
|
||||||
|
)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
"¿Qué familiar eres?",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomDropdown(
|
||||||
|
items: [Text("Padre"), Text("Madre"), Text("Tutor")],
|
||||||
|
hint: "¿Qué familiar eres?",
|
||||||
|
onChanged: (value)=>setState(() {
|
||||||
|
relation = value;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CustomTextField(label: "Dirección completa", hint: "Calle Gran Vía 30 6º, 28013"),
|
||||||
|
CustomTextField(label: "Ciudad", hint: "Ciudad"),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
"País",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomDropdown(
|
||||||
|
items: [Text("España"), Text("Francia"), Text("Portugal")],
|
||||||
|
hint: "País",
|
||||||
|
onChanged: (value)=>setState(() {
|
||||||
|
country = value;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CustomTextField(label: "Nacionalidad", hint: "España"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
modules/auth/lib/src/features/sign_up/signup_builder.dart
Normal file
18
modules/auth/lib/src/features/sign_up/signup_builder.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:auth/src/features/sign_up/signup_screen.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
|
class SignupBuilder {
|
||||||
|
const SignupBuilder();
|
||||||
|
|
||||||
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
|
||||||
|
|
||||||
|
return MaterialPage<void>(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: SignupScreen(navigationContract: navigationContract),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SignupPersonalScreen extends StatelessWidget{
|
||||||
|
const SignupPersonalScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
CustomTextField(label: "Nombre", hint: "Nombre"),
|
||||||
|
CustomTextField(label: "Apellidos", hint: "Apellidos"),
|
||||||
|
CustomTextField(label: "DNI", hint: "DNI"),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
/*CustomDropdown(
|
||||||
|
value: 0,
|
||||||
|
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
|
||||||
|
onChanged: (value)=> {},
|
||||||
|
width: 80,
|
||||||
|
),*/
|
||||||
|
Expanded(child: CustomTextField(
|
||||||
|
label: "Teléfono móvil",
|
||||||
|
hint: "123456789",
|
||||||
|
numeric: true
|
||||||
|
))
|
||||||
|
]
|
||||||
|
),
|
||||||
|
CustomTextField(label: "Correo electrónico", hint: "Correo electrónico"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
134
modules/auth/lib/src/features/sign_up/signup_screen.dart
Normal file
134
modules/auth/lib/src/features/sign_up/signup_screen.dart
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import 'package:auth/src/widgets/layouts/form_step_layout.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:auth/src/features/sign_up/signup_address_screen.dart';
|
||||||
|
import 'package:auth/src/features/sign_up/signup_personal_screen.dart';
|
||||||
|
import 'package:auth/src/features/sign_up/signup_user_screen.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
|
import 'account_created_screen.dart';
|
||||||
|
|
||||||
|
class SignupScreen extends ConsumerStatefulWidget {
|
||||||
|
NavigationContract navigationContract;
|
||||||
|
|
||||||
|
SignupScreen({super.key, required this.navigationContract});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<SignupScreen> createState() => _SignupScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignupScreenState extends ConsumerState<SignupScreen> {
|
||||||
|
int currentStep = 0;
|
||||||
|
bool acceptTerms = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return getSteps()[currentStep];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> getSteps() {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return [
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Crea tu usuario",
|
||||||
|
subtitle:
|
||||||
|
"Con tu email y tu número podremos mantenerte siempre informado",
|
||||||
|
supertitle: "Usuario y contacto",
|
||||||
|
currentStep: 1,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [SignupPersonalScreen()],
|
||||||
|
footer: [
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Atrás",
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
text: "Siguiente",
|
||||||
|
size: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: acceptTerms,
|
||||||
|
onChanged: (_) => setState(() {
|
||||||
|
acceptTerms = !acceptTerms;
|
||||||
|
}),
|
||||||
|
title: Text(
|
||||||
|
"Acepto los términos y condiciones",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
checkboxScaleFactor: 1.5,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
nextStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
previousStep: () => {},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Tu dirección",
|
||||||
|
subtitle:
|
||||||
|
"Tu dirección nos ayudará a verificar y mantener la seguridad de tu cuenta",
|
||||||
|
supertitle: "Domicilio",
|
||||||
|
currentStep: 2,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [SignupAddressScreen()],
|
||||||
|
nextStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
previousStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep--;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FormStepLayout(
|
||||||
|
title: "Identifícate",
|
||||||
|
subtitle:
|
||||||
|
"Contraseña mínima de 8 caracteres, con una mayúscula, un número y un carácter especial",
|
||||||
|
supertitle: "Usuario y contacto",
|
||||||
|
currentStep: 3,
|
||||||
|
numSteps: 3,
|
||||||
|
body: [SignupUserScreen()],
|
||||||
|
nextStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep++;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
previousStep: () => {
|
||||||
|
setState(() {
|
||||||
|
currentStep--;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AccountCreatedScreen(
|
||||||
|
navigationContract: widget.navigationContract,
|
||||||
|
kidAccount: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SignupUserScreen extends StatefulWidget{
|
||||||
|
const SignupUserScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SignupUserScreen> createState() => SignupUserScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignupUserScreenState extends State<SignupUserScreen>{
|
||||||
|
bool passwordVisible=false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
showPassword: passwordVisible,
|
||||||
|
label: "Contraseña",
|
||||||
|
hint: "********"
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
showPassword: passwordVisible,
|
||||||
|
label: "Repetir contraseña",
|
||||||
|
hint: "*******"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:navigation/navigation.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class LinkPhoneScreen extends ConsumerWidget {
|
|
||||||
final NavigationContract navigationContract;
|
|
||||||
|
|
||||||
const LinkPhoneScreen({super.key, required this.navigationContract});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
TextEditingController phoneController = TextEditingController();
|
|
||||||
// String? phone;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"¡Nos alegra mucho tenerte por aquí!",
|
|
||||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Para poder entrar de forma segura, te vamos a enviar un código al teléfono",
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
DropdownMenu(
|
|
||||||
initialSelection: "es",
|
|
||||||
dropdownMenuEntries: List<DropdownMenuEntry>.generate(3, (
|
|
||||||
int index,
|
|
||||||
) {
|
|
||||||
return DropdownMenuEntry(
|
|
||||||
labelWidget: Icon(Icons.outlined_flag),
|
|
||||||
label: "es",
|
|
||||||
value: "es",
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
onSubmitted: (String value) {
|
|
||||||
// phone = value;
|
|
||||||
},
|
|
||||||
controller: phoneController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Teléfono móvil",
|
|
||||||
hintText: "Teléfono",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: () =>
|
|
||||||
navigationContract.pushTo(AppRoutes.phoneCode),
|
|
||||||
child: Text("Siguiente"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import 'package:auth/src/login/presentation/loading_google_screen.dart';
|
|
||||||
import 'package:auth/src/sign_up/signup_screen.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:navigation/navigation.dart';
|
|
||||||
import 'package:sf_localizations/sf_localizations.dart';
|
|
||||||
|
|
||||||
class LoginScreen extends ConsumerStatefulWidget {
|
|
||||||
final NavigationContract navigationContract;
|
|
||||||
|
|
||||||
const LoginScreen({super.key, required this.navigationContract});
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<LoginScreen> createState() => _LoginScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|
||||||
bool passwordVisible = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// final l10n = SfLocalizations.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.all(30),
|
|
||||||
child: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check, color: Color(0xFF329e95), size: 50),
|
|
||||||
const Text(
|
|
||||||
// context.translate(I18n.example)
|
|
||||||
'¡Te damos la bienvenida!',
|
|
||||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Nombre de usuario',
|
|
||||||
labelText: 'Nombre de usuario',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
obscureText: !passwordVisible,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Contraseña',
|
|
||||||
hintText: '********',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
passwordVisible ? Icons.visibility : Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
widget.navigationContract.pushTo(AppRoutes.recoverPassword),
|
|
||||||
child: const Text('¿Has olvidado la contraseña?'),
|
|
||||||
),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () =>
|
|
||||||
widget.navigationContract.pushTo(AppRoutes.dashboardHome),
|
|
||||||
child: const Text('Iniciar sesión'),
|
|
||||||
),
|
|
||||||
const Stack(children: [Divider(), Text('o continúa con')]),
|
|
||||||
Row(
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => const LoadingGoogleScreen(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('Google', semanticsLabel: 'Google'),
|
|
||||||
),
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: const Icon(Icons.apple, semanticLabel: 'Apple'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Text('¿No tienes cuenta?'),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => const SignupScreen()),
|
|
||||||
),
|
|
||||||
child: const Text('Crear una ahora'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:navigation/navigation.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
// import 'package:sf_app_platform/payments/view/screens/core/dashboard_screen.dart';
|
|
||||||
// import 'package:sf_app_platform/payments/view/screens/login_screen.dart';
|
|
||||||
|
|
||||||
class PhoneCodeScreen extends ConsumerWidget {
|
|
||||||
// final String phone;
|
|
||||||
final NavigationContract navigationContract;
|
|
||||||
|
|
||||||
PhoneCodeScreen({super.key, required this.navigationContract});
|
|
||||||
// const PhoneCodeScreen({super.key, required this.phone});
|
|
||||||
|
|
||||||
// class PhoneCodeScreenState extends State<PhoneCodeScreen> {
|
|
||||||
final focusNodes = List<FocusNode>.generate(6, (int i) {
|
|
||||||
return FocusNode();
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 15,
|
|
||||||
children: [
|
|
||||||
Spacer(flex: 8),
|
|
||||||
Text(
|
|
||||||
"Conéctate",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
|
||||||
),
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
text: "Hemos enviado el código al ",
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
// text: widget.phone,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text("Introduce el código aquí"),
|
|
||||||
Row(
|
|
||||||
spacing: 20,
|
|
||||||
children: List<Widget>.generate(6, (int i) {
|
|
||||||
return Expanded(
|
|
||||||
child: TextField(
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "0",
|
|
||||||
counterText: "",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLength: 1,
|
|
||||||
onChanged: (String value) => {
|
|
||||||
value != ""
|
|
||||||
? focusNodes[i + 1].requestFocus()
|
|
||||||
: focusNodes[i - 1].requestFocus(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => {navigationContract.pushTo(AppRoutes.login)},
|
|
||||||
child: Text("Entrar"),
|
|
||||||
),
|
|
||||||
Text("¿No lo has recibido?"),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text(
|
|
||||||
"Volver a intentarlo",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(flex: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:navigation/navigation.dart';
|
|
||||||
|
|
||||||
class WelcomeScreen extends ConsumerWidget {
|
|
||||||
final NavigationContract navigationContract;
|
|
||||||
|
|
||||||
const WelcomeScreen({super.key, required this.navigationContract});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Spacer(),
|
|
||||||
Expanded(
|
|
||||||
child: CarouselView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemExtent: double.infinity,
|
|
||||||
itemSnapping: true,
|
|
||||||
shrinkExtent: 400,
|
|
||||||
children: generateSteps(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => navigationContract.goTo(AppRoutes.linkPhone),
|
|
||||||
child: const Text('Continuar'),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jumpToNext(BuildContext context) {
|
|
||||||
// Navigator.pushReplacement(
|
|
||||||
// context,
|
|
||||||
// MaterialPageRoute(builder: (_) => LinkPhoneScreen()),
|
|
||||||
// );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> generateSteps() {
|
|
||||||
return [
|
|
||||||
Column(
|
|
||||||
spacing: 30,
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"),
|
|
||||||
Text(
|
|
||||||
"Aprende a gestionar su dinero",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
|
||||||
),
|
|
||||||
Text("Tu peque crea hábitos y se divierte mientras lo hace"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset("assets/images/ui/bienvenida_paso2.svg"),
|
|
||||||
Text("Tranquilidad en cada pago que hacen"),
|
|
||||||
Text("Supervisa gastos, fija límites y acompáñalos en cada paso"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset("assets/images/ui/bienvenida_paso3.svg"),
|
|
||||||
Text("Pagos fáciles y seguros en sus manos"),
|
|
||||||
Text("Podrá pagar desde su reloj.\n Sin móvil ni efectivo"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class EmailSentScreen extends ConsumerWidget {
|
|
||||||
final String email;
|
|
||||||
|
|
||||||
const EmailSentScreen({super.key, required this.email});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext contex, WidgetRef ref) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
Spacer(flex: 8),
|
|
||||||
Text(
|
|
||||||
"Recuperar contraseña",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
|
||||||
),
|
|
||||||
Spacer(flex: 1),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Correo enviado correctamente",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Spacer(flex: 1),
|
|
||||||
Text(
|
|
||||||
"Revisa tu email y haz clic en el enlace para crear una nueva contraseña",
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"",
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text("Reenviar correo"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: () => {
|
|
||||||
// Navigator.push(
|
|
||||||
// context,
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (_) => NewPasswordScreen(),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
|
||||||
theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text("Continuar"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Spacer(flex: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class NewPasswordScreen extends ConsumerStatefulWidget {
|
|
||||||
const NewPasswordScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<NewPasswordScreen> createState() => NewPasswordScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
|
||||||
bool passwordVisible = false;
|
|
||||||
bool equalPasswords = false;
|
|
||||||
String password = '';
|
|
||||||
|
|
||||||
Map<String, bool> securityChecks = {
|
|
||||||
'min': false,
|
|
||||||
'capital': false,
|
|
||||||
'number': false,
|
|
||||||
'special': false,
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
passwordVisible = false;
|
|
||||||
equalPasswords = false;
|
|
||||||
password = '';
|
|
||||||
securityChecks = {
|
|
||||||
'min': false,
|
|
||||||
'capital': false,
|
|
||||||
'number': false,
|
|
||||||
'special': false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: const EdgeInsets.all(30),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
const Spacer(flex: 4),
|
|
||||||
const Text(
|
|
||||||
'Recuperar contraseña',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
obscureText: !passwordVisible,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Nueva contraseña',
|
|
||||||
hintText: '********',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
passwordVisible ? Icons.visibility : Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
password = value;
|
|
||||||
securityChecks = checkSecurity(value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
obscureText: !passwordVisible,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Repetir contraseña',
|
|
||||||
hintText: '********',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
passwordVisible ? Icons.visibility : Icons.visibility_off,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
equalPasswords = password == value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
securityChecks['min']!
|
|
||||||
? Icons.check
|
|
||||||
: Icons.cancel_outlined,
|
|
||||||
color: securityChecks['min']!
|
|
||||||
? theme.getColorFor(ThemeCode.buttonPrimary)
|
|
||||||
: theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Al menos 8 caracteres'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
securityChecks['capital']!
|
|
||||||
? Icons.check
|
|
||||||
: Icons.cancel_outlined,
|
|
||||||
color: securityChecks['capital']!
|
|
||||||
? theme.getColorFor(ThemeCode.buttonPrimary)
|
|
||||||
: theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Una mayúscula'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
securityChecks['number']!
|
|
||||||
? Icons.check
|
|
||||||
: Icons.cancel_outlined,
|
|
||||||
color: securityChecks['number']!
|
|
||||||
? theme.getColorFor(ThemeCode.buttonPrimary)
|
|
||||||
: theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Un número'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
securityChecks['special']!
|
|
||||||
? Icons.check
|
|
||||||
: Icons.cancel_outlined,
|
|
||||||
color: securityChecks['special']!
|
|
||||||
? theme.getColorFor(ThemeCode.buttonPrimary)
|
|
||||||
: theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Text('Un carácter especial'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(flex: 1),
|
|
||||||
FilledButton(
|
|
||||||
onPressed:
|
|
||||||
equalPasswords &&
|
|
||||||
securityChecks.values.every((check) => check)
|
|
||||||
? () {}
|
|
||||||
: null,
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: const Center(child: Text('Aceptar')),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(flex: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Extraer de la vista
|
|
||||||
Map<String, bool> checkSecurity(String value) {
|
|
||||||
return {
|
|
||||||
'min': value.length >= 8,
|
|
||||||
'capital': RegExp(r'[A-Z]').hasMatch(value),
|
|
||||||
'number': RegExp(r'[0-9]').hasMatch(value),
|
|
||||||
'special': RegExp(r'[^A-Za-z0-9]').hasMatch(value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import 'package:auth/src/recover_password/presentation/email_sent_screen.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:navigation/navigation.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class RestorePasswordScreen extends ConsumerWidget {
|
|
||||||
final NavigationContract navigationContract;
|
|
||||||
|
|
||||||
const RestorePasswordScreen({super.key, required this.navigationContract});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
margin: EdgeInsets.all(30),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
spacing: 30,
|
|
||||||
children: [
|
|
||||||
Spacer(flex: 8),
|
|
||||||
Text(
|
|
||||||
"Recuperar contaseña",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Introduce tu email para enviarte un enlace de recuperación",
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Correo electrónico",
|
|
||||||
hintText: "Correo electrónico",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () => {Navigator.pop(context)},
|
|
||||||
child: Text("Volver"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: () => {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => EmailSentScreen(email: ""),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
|
||||||
theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text("Enviar"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Spacer(flex: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class SignupAddressScreen extends StatelessWidget {
|
|
||||||
const SignupAddressScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
spacing: 30,
|
|
||||||
children: [
|
|
||||||
Text("Domicilio"),
|
|
||||||
Text(
|
|
||||||
"Tu dirección",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Tu dirección nos ayuda a verificar y mantener la seguridad de tu cuenta",
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "Dirección completa",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "Ciudad",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DropdownMenu(
|
|
||||||
dropdownMenuEntries: List<DropdownMenuEntry>.generate(3, (int index) {
|
|
||||||
return DropdownMenuEntry(value: "España", label: "España");
|
|
||||||
}),
|
|
||||||
hintText: "País",
|
|
||||||
width: double.infinity,
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "Nacionalidad",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class SignupPersonalScreen extends StatelessWidget{
|
|
||||||
const SignupPersonalScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
spacing: 30,
|
|
||||||
children: [
|
|
||||||
Text("Datos personales"),
|
|
||||||
Text("Identifícate", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30)),
|
|
||||||
Text("Nos aseguraremos de que la cuenta está a nombre del adulto responsable"),
|
|
||||||
TextField(decoration: InputDecoration(labelText: "Nombre", hintText: "Nombre", border: OutlineInputBorder())),
|
|
||||||
TextField(decoration: InputDecoration(labelText: "Apellidos", hintText: "Apellidos", border: OutlineInputBorder())),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded( child: TextField(
|
|
||||||
decoration: InputDecoration(label: Text("Fecha de nacimiento"), hintText: "DD", border: OutlineInputBorder()),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
||||||
)),
|
|
||||||
Expanded( child: TextField(
|
|
||||||
decoration: InputDecoration(hintText: "MM", border: OutlineInputBorder()),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
||||||
)),
|
|
||||||
Expanded( child: TextField(
|
|
||||||
decoration: InputDecoration(hintText: "AAAA", border: OutlineInputBorder()),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
DropdownMenu(
|
|
||||||
width: double.infinity,
|
|
||||||
label: Text("¿Qué familiar eres?"),
|
|
||||||
dropdownMenuEntries: [
|
|
||||||
DropdownMenuEntry(label: "Padre", value: "Padre"),
|
|
||||||
DropdownMenuEntry(label: "Madre", value: "Madre"),
|
|
||||||
DropdownMenuEntry(label: "Tutor", value: "Tutor"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import 'package:auth/src/sign_up/account_created_screen.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:auth/src/sign_up/signup_address_screen.dart';
|
|
||||||
import 'package:auth/src/sign_up/signup_personal_screen.dart';
|
|
||||||
import 'package:auth/src/sign_up/signup_user_screen.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class SignupScreen extends ConsumerStatefulWidget {
|
|
||||||
const SignupScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<SignupScreen> createState() => _SignupScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SignupScreenState extends ConsumerState<SignupScreen> {
|
|
||||||
int currentStep = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Container(
|
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: SizedBox(
|
|
||||||
child: Stepper(
|
|
||||||
type: StepperType.horizontal,
|
|
||||||
currentStep: currentStep,
|
|
||||||
steps: getSteps(),
|
|
||||||
controlsBuilder:
|
|
||||||
(BuildContext context, ControlsDetails controls) {
|
|
||||||
final canGoBack = currentStep > 0;
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: canGoBack ? controls.onStepCancel : null,
|
|
||||||
child: const Text('Atrás'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
|
||||||
theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: controls.onStepContinue,
|
|
||||||
child: const Text('Siguiente'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onStepCancel: () {
|
|
||||||
if (currentStep > 0) {
|
|
||||||
setState(() {
|
|
||||||
currentStep -= 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onStepContinue: () {
|
|
||||||
final isLastStep = currentStep == getSteps().length - 1;
|
|
||||||
|
|
||||||
if (isLastStep) {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => const AccountCreatedScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
currentStep += 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Step> getSteps() {
|
|
||||||
return <Step>[
|
|
||||||
Step(
|
|
||||||
state: currentStep > 0 ? StepState.complete : StepState.indexed,
|
|
||||||
isActive: currentStep >= 0,
|
|
||||||
stepStyle: currentStep >= 0
|
|
||||||
? const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.transparent,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 5),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: const Text(""),
|
|
||||||
content: const SignupPersonalScreen(),
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
state: currentStep > 1 ? StepState.complete : StepState.indexed,
|
|
||||||
isActive: currentStep >= 1,
|
|
||||||
stepStyle: currentStep >= 1
|
|
||||||
? const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 1),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: const Text(""),
|
|
||||||
content: const SignupAddressScreen(),
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
state: currentStep > 2 ? StepState.complete : StepState.indexed,
|
|
||||||
isActive: currentStep >= 2,
|
|
||||||
stepStyle: currentStep >= 2
|
|
||||||
? const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Color(0xFF329e95),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
)
|
|
||||||
: const StepStyle(
|
|
||||||
connectorThickness: 0,
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: BoxShadow(spreadRadius: 1),
|
|
||||||
indexStyle: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
title: const Text(""),
|
|
||||||
content: const SignupUserScreen(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class SignupUserScreen extends StatefulWidget{
|
|
||||||
const SignupUserScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SignupUserScreen> createState() => SignupUserScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignupUserScreenState extends State<SignupUserScreen>{
|
|
||||||
bool passwordVisible=false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
spacing: 30,
|
|
||||||
children: [
|
|
||||||
Text("Usuario y contacto"),
|
|
||||||
Text("Crea tu usuario", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30)),
|
|
||||||
Text("Con tu email y tu número podremos mantenerte siempre informado"),
|
|
||||||
TextField(decoration: InputDecoration(labelText: "Correo electrónico", hintText: "Correo electrónico", border: OutlineInputBorder())),
|
|
||||||
Row(children: [
|
|
||||||
DropdownMenu(
|
|
||||||
initialSelection: "es",
|
|
||||||
dropdownMenuEntries: List<DropdownMenuEntry>.generate(3, (int index){
|
|
||||||
return DropdownMenuEntry(labelWidget: Icon(Icons.outlined_flag), label: "es", value: "es");
|
|
||||||
})
|
|
||||||
),
|
|
||||||
Expanded(child: TextField(
|
|
||||||
decoration: InputDecoration(labelText: "Teléfono móvil", hintText: "Teléfono", border: OutlineInputBorder()),
|
|
||||||
keyboardType: TextInputType.number)
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
TextField(
|
|
||||||
obscureText: passwordVisible,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Contraseña",
|
|
||||||
hintText: "********",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(passwordVisible
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
TextField(obscureText: passwordVisible,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Repetir contraseña",
|
|
||||||
hintText: "*******",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(passwordVisible
|
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
passwordVisible = !passwordVisible;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
94
modules/auth/lib/src/widgets/layouts/form_step_layout.dart
Normal file
94
modules/auth/lib/src/widgets/layouts/form_step_layout.dart
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class FormStepLayout extends ConsumerWidget{
|
||||||
|
final int currentStep;
|
||||||
|
final int numSteps;
|
||||||
|
final String? supertitle;
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final List<Widget> body;
|
||||||
|
final List<Widget>? footer;
|
||||||
|
final VoidCallback nextStep;
|
||||||
|
final VoidCallback previousStep;
|
||||||
|
|
||||||
|
const FormStepLayout({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.supertitle,
|
||||||
|
required this.currentStep,
|
||||||
|
required this.numSteps,
|
||||||
|
required this.body,
|
||||||
|
this.footer,
|
||||||
|
required this.nextStep,
|
||||||
|
required this.previousStep
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
body: SafeArea(child: SingleChildScrollView(child: Container(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
padding: EdgeInsets.only(top: 40, left: 24, right: 24),
|
||||||
|
child: Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
StepIndicator(
|
||||||
|
total: numSteps,
|
||||||
|
current: currentStep,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
if (supertitle!=null) Text(supertitle!, textAlign: TextAlign.center, style: TextStyle(fontSize: 18)),
|
||||||
|
Text(title, textAlign: TextAlign.center, style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
|
||||||
|
if (subtitle!=null) Text(subtitle!, textAlign: TextAlign.center, style: TextStyle(fontSize: 18)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 40,
|
||||||
|
children: [
|
||||||
|
...body,
|
||||||
|
if (footer==null) navigationButtons(currentStep, numSteps, nextStep, previousStep, theme),
|
||||||
|
...(footer?? [])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget navigationButtons(int currentStep, int numSteps, VoidCallback nextStep, VoidCallback previousStep, ThemePort theme) {
|
||||||
|
if (currentStep == numSteps){
|
||||||
|
return PrimaryButton(
|
||||||
|
onPressed: nextStep,
|
||||||
|
text: "Continuar",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(child: SecondaryButton(
|
||||||
|
onPressed: previousStep,
|
||||||
|
text: "Atrás",
|
||||||
|
size: 16,
|
||||||
|
)),
|
||||||
|
Expanded(child: PrimaryButton(
|
||||||
|
onPressed: nextStep,
|
||||||
|
text: "Siguiente",
|
||||||
|
size: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary)
|
||||||
|
))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,17 +22,25 @@ dependencies:
|
|||||||
path: ../../packages/navigation
|
path: ../../packages/navigation
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
#dependencies go here
|
#dependencies go here
|
||||||
flutter_svg: ^2.2.1
|
flutter_svg: ^2.2.1
|
||||||
get_it: ^9.0.5
|
get_it: ^9.0.5
|
||||||
go_router: ^17.0.0
|
go_router: ^17.0.0
|
||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.0.3
|
||||||
|
freezed_annotation: ^3.1.0
|
||||||
|
freezed: ^3.2.3
|
||||||
|
dio: ^5.9.0
|
||||||
|
country_code_picker: ^3.4.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
riverpod_generator: ^3.0.3
|
||||||
|
build_runner: ^2.7.1
|
||||||
|
riverpod_lint: ^3.0.3
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
dashboard_shell:
|
dashboard_shell:
|
||||||
path: ../dashboard_shell
|
path: ../dashboard_shell
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
home:
|
home:
|
||||||
path: ../home
|
path: ../home
|
||||||
navigation:
|
navigation:
|
||||||
@@ -12,6 +14,8 @@ dependency_overrides:
|
|||||||
path: ../notifications
|
path: ../notifications
|
||||||
profile:
|
profile:
|
||||||
path: ../profile
|
path: ../profile
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
sf_shared:
|
sf_shared:
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
auth:
|
auth:
|
||||||
path: ../auth
|
path: ../auth
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
home:
|
home:
|
||||||
path: ../home
|
path: ../home
|
||||||
navigation:
|
navigation:
|
||||||
@@ -12,6 +14,8 @@ dependency_overrides:
|
|||||||
path: ../notifications
|
path: ../notifications
|
||||||
profile:
|
profile:
|
||||||
path: ../profile
|
path: ../profile
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
sf_shared:
|
sf_shared:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:home/src/presentation/wallet_management_layout.dart';
|
import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -32,13 +31,10 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
PrimaryButton(
|
||||||
onPressed: () {},
|
onPressed: ()=>{},
|
||||||
child: Container(
|
text: "Añadir dinero",
|
||||||
width: double.infinity,
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: const Center(child: Text('Añadir dinero')),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
@@ -61,14 +57,10 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
'Ingresar dinero en el wallet',
|
'Ingresar dinero en el wallet',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||||
),
|
),
|
||||||
TextField(
|
CustomTextField(
|
||||||
decoration: const InputDecoration(
|
numeric: true,
|
||||||
labelText: 'Cantidad',
|
label: "Cantidad",
|
||||||
hintText: '0€',
|
hint: "0€",
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
||||||
),
|
),
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
@@ -93,6 +85,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
),
|
),
|
||||||
const Text('Este dato aparecerá en el reloj del peque'),
|
const Text('Este dato aparecerá en el reloj del peque'),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Paga semanal'),
|
title: const Text('Paga semanal'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: reason == 'weekly',
|
value: reason == 'weekly',
|
||||||
@@ -104,6 +97,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Objetivo semanal cumplido'),
|
title: const Text('Objetivo semanal cumplido'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: reason == 'goal',
|
value: reason == 'goal',
|
||||||
@@ -115,6 +109,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Gastos extraordinarios'),
|
title: const Text('Gastos extraordinarios'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: reason == 'extraordinary',
|
value: reason == 'extraordinary',
|
||||||
@@ -126,6 +121,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Otro'),
|
title: const Text('Otro'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: reason == 'other',
|
value: reason == 'other',
|
||||||
@@ -136,16 +132,11 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
},
|
},
|
||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
TextField(
|
CustomTextField(
|
||||||
minLines: 3,
|
lines: 3,
|
||||||
maxLines: 3,
|
length: 150,
|
||||||
maxLength: 150,
|
label: "Escribir mensaje a ${widget.kid.name} del motivo del ingreso",
|
||||||
decoration: InputDecoration(
|
hint: "Escribe tu mensaje",
|
||||||
labelText:
|
|
||||||
'Escribir mensaje a ${widget.kid.name} del motivo del ingreso',
|
|
||||||
hintText: 'Escribe tu mensaje',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
@@ -170,6 +161,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
),
|
),
|
||||||
const Text('Este dato aparecerá en el reloj del peque'),
|
const Text('Este dato aparecerá en el reloj del peque'),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Ahora'),
|
title: const Text('Ahora'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: !program,
|
value: !program,
|
||||||
@@ -181,6 +173,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Programar'),
|
title: const Text('Programar'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: program,
|
value: program,
|
||||||
@@ -191,17 +184,11 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
|||||||
},
|
},
|
||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
if (program)
|
if (program) CustomTextField(),
|
||||||
const TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Fecha / hora (placeholder)',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
111
modules/home/lib/src/presentation/extract_screen.dart
Normal file
111
modules/home/lib/src/presentation/extract_screen.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class ExtractScreen extends ConsumerWidget{
|
||||||
|
final Kid kid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ExtractScreen({
|
||||||
|
super.key,
|
||||||
|
required this.kid,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return WalletManagementLayout(
|
||||||
|
kid: kid,
|
||||||
|
children: [Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Retirar dinero de la cuenta",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Este dato aparecerá en el reloj del peque",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0)
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
label: "Selecciona la cantidad de dinero",
|
||||||
|
hint: "2€",
|
||||||
|
numeric: true,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Este es el mensaje fijado por defecto:",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0)
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"\"Hemos quitado el dinero del reloj, ya no puedes pagar con él\"",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Escribir mensaje a ${kid.name} del motivo de la retirada de su dinero",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0)
|
||||||
|
)),
|
||||||
|
CustomTextField(
|
||||||
|
hint: "Escribe tu mensaje",
|
||||||
|
lines: 4,
|
||||||
|
length: 150,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 16),
|
||||||
|
Text("Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
footer: Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.only(topRight: Radius.circular(24), topLeft: Radius.circular(24))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: ()=>{Navigator.pop(context)},
|
||||||
|
text: "Enviar mensaje y bloquear",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: ()=>Navigator.pop(context),
|
||||||
|
child: Text("Cancelar", style: TextStyle(fontSize: 18))
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
786
modules/home/lib/src/presentation/goals_screen.dart
Normal file
786
modules/home/lib/src/presentation/goals_screen.dart
Normal file
@@ -0,0 +1,786 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class GoalsScreen extends ConsumerStatefulWidget{
|
||||||
|
final Kid kid;
|
||||||
|
List<Task> tasks = [Task(rewardAmount: 10, subtasks: [
|
||||||
|
Subtask(name: "Hacer la cama", completed: false),
|
||||||
|
Subtask(name: "Terminar los deberes", completed: true),
|
||||||
|
Subtask(name: "Recoger la mesa", completed: true),
|
||||||
|
Subtask(name: "Lavarse los dientes", completed: false)
|
||||||
|
]), Task(rewardAmount: 10)];
|
||||||
|
final List<SavingsGoal> savingsGoals;
|
||||||
|
|
||||||
|
@override
|
||||||
|
GoalsScreen({
|
||||||
|
super.key,
|
||||||
|
required this.kid,
|
||||||
|
this.savingsGoals = const [
|
||||||
|
SavingsGoal(name: "Cumpleaños de Emma", goal: 24, saved: 12.09),
|
||||||
|
SavingsGoal(name: "Protecciones nuevas de patines", goal: 10, saved: 0)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<GoalsScreen> createState() => GoalsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GoalsScreenState extends ConsumerState<GoalsScreen>{
|
||||||
|
late bool fullPlan;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fullPlan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return WalletManagementLayout(
|
||||||
|
kid: widget.kid,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text("Metas", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)),
|
||||||
|
Icon(Icons.workspace_premium),
|
||||||
|
Spacer(),
|
||||||
|
Text("Sólo con Plan Completo", style: TextStyle(fontSize: 14, letterSpacing: 0))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SavingsBlock(fullPlan: fullPlan, savings: widget.savingsGoals),
|
||||||
|
TasksBlock(fullPlan: fullPlan, tasks: widget.tasks),
|
||||||
|
],
|
||||||
|
footer: Container()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SavingsBlock extends ConsumerStatefulWidget {
|
||||||
|
final bool fullPlan;
|
||||||
|
final List savings;
|
||||||
|
|
||||||
|
@override
|
||||||
|
const SavingsBlock({
|
||||||
|
super.key,
|
||||||
|
required this.fullPlan,
|
||||||
|
required this.savings
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<SavingsBlock> createState() => SavingsBlockState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SavingsBlockState extends ConsumerState<SavingsBlock>{
|
||||||
|
late List<bool> showEdit;
|
||||||
|
late List<bool> showAdd;
|
||||||
|
late bool showNew;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
showEdit = widget.savings.map((_)=>false).toList();
|
||||||
|
showAdd = widget.savings.map((_)=>false).toList();
|
||||||
|
showNew = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
var emptyBlock = ({fullPlan}) => Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/ahorros.svg")),
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Ahorros",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Text(
|
||||||
|
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
if (fullPlan) Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SecondaryButton(
|
||||||
|
onPressed: ()=>setState(() {
|
||||||
|
showNew = true;
|
||||||
|
}),
|
||||||
|
text: "Crear objetivo de ahorro",
|
||||||
|
radius: 8,
|
||||||
|
height: 44,
|
||||||
|
size: 14,
|
||||||
|
width: 230,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
|
||||||
|
child: Text(
|
||||||
|
"Ver estado de ahorros",
|
||||||
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!fullPlan) TextButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.workspace_premium, size: 16),
|
||||||
|
Text("Suscribirme al Plan Completo")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
var editBlock = ({create, index}) => Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
hint: create? "Ponle un título para reconocerlo" : widget.savings[index].name,
|
||||||
|
label: "Motivo del ahorro",
|
||||||
|
lines: create? 2 : 1,
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
hint: "30€",
|
||||||
|
label: "Seleciona la cantidad a ahorrar",
|
||||||
|
numeric: true,
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: false,
|
||||||
|
onChanged: (_) => {},
|
||||||
|
checkboxScaleFactor: 2,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: Text(
|
||||||
|
"Ahorro automático desde su paga",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
hint: "2€",
|
||||||
|
label: "Selecciona la cantidad de dinero",
|
||||||
|
numeric: true,
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: false,
|
||||||
|
onChanged: (_) => {},
|
||||||
|
checkboxScaleFactor: 2,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: Text(
|
||||||
|
"Mandar automáticamente el dinero al reloj del peque cuando consiga su objetivo",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Este es el mensaje fijado por defecto que le llegará a su reloj:",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"\"¡Genial, has conseguido ahorrar lo que querías!\"",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
hint: "Escribe tu mensaje",
|
||||||
|
label: "Escribir otro mensaje diferente:",
|
||||||
|
lines: 4,
|
||||||
|
length: 150,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 16,),
|
||||||
|
Text(
|
||||||
|
"Máximo 150 caracteres",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Guardar cambios",
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (create){
|
||||||
|
setState(() {
|
||||||
|
showNew = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
showEdit[index] = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Cancelar",
|
||||||
|
style: TextStyle(fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (widget.fullPlan) {
|
||||||
|
if (showNew){
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
|
border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Ahorros",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
|
),
|
||||||
|
child: editBlock(create: true, index: null)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (widget.savings.isNotEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
//border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
"Ahorros",
|
||||||
|
style: TextStyle(fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
...List<Widget>.generate(widget.savings.length, (int index) =>
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: BoxBorder.all(
|
||||||
|
color: theme.getColorFor(ThemeCode.textPrimary)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
"Ahorro para",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
widget.savings[index].name,
|
||||||
|
style: TextStyle(fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.backgroundTertiary),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(16)),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 8, vertical: 4),
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.account_balance, size: 24,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary)),
|
||||||
|
MoneyText(
|
||||||
|
text: "${widget.savings[index]
|
||||||
|
.goal}€",
|
||||||
|
size: 30,
|
||||||
|
secondarySize: 14,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
if (index == 0)Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
ProgressBar(
|
||||||
|
max: widget.savings[index].goal+0.0,
|
||||||
|
value: widget.savings[index].saved,
|
||||||
|
height: 64,
|
||||||
|
textSize: 40,
|
||||||
|
textSecondarySize: 24,
|
||||||
|
backgroundColor: theme.getColorFor(
|
||||||
|
ThemeCode.backgroundTertiary),
|
||||||
|
foregroundColor: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary),
|
||||||
|
textColor: theme.getColorFor(
|
||||||
|
ThemeCode.textSecondary)
|
||||||
|
),
|
||||||
|
Center(child: Text("Ahorrado")),
|
||||||
|
if (!showAdd[index]) TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStatePropertyAll(
|
||||||
|
EdgeInsets.zero)),
|
||||||
|
onPressed: () =>
|
||||||
|
setState(() {
|
||||||
|
showAdd[index] = true;
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
"+ Añadir dinero extra a este ahorro",
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
if (showAdd[index]) CustomTextField(
|
||||||
|
hint: "5€",
|
||||||
|
numeric: true,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!showEdit[index]) Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStatePropertyAll(
|
||||||
|
EdgeInsets.zero)),
|
||||||
|
onPressed: () =>
|
||||||
|
setState(() {
|
||||||
|
showEdit[index] = true;
|
||||||
|
}),
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.edit_outlined, size: 24),
|
||||||
|
Text(
|
||||||
|
"Editar",
|
||||||
|
style: TextStyle(fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStatePropertyAll(
|
||||||
|
EdgeInsets.zero)),
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.delete_outline_outlined,
|
||||||
|
size: 24),
|
||||||
|
Text(
|
||||||
|
"Eliminar",
|
||||||
|
style: TextStyle(fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if(showEdit[index]) editBlock(
|
||||||
|
create: false, index: index)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
setState(() {
|
||||||
|
showNew = true;
|
||||||
|
}),
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Icon(Icons.account_balance, size: 24),
|
||||||
|
Text(
|
||||||
|
"Crear otro ahorro",
|
||||||
|
style: TextStyle(fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return emptyBlock(fullPlan: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return emptyBlock(fullPlan: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TasksBlock extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
|
final bool fullPlan;
|
||||||
|
final List<Task> tasks;
|
||||||
|
|
||||||
|
@override
|
||||||
|
const TasksBlock({
|
||||||
|
super.key,
|
||||||
|
required this.fullPlan,
|
||||||
|
required this.tasks
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<ConsumerStatefulWidget> createState() => TasksBlockState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TasksBlockState extends ConsumerState<TasksBlock>{
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
final emptyBlock = ({fullPlan})=>Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/tareas.svg")),
|
||||||
|
Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Tareas",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Text(
|
||||||
|
"Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
if (fullPlan) Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SecondaryButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
text: "Crear lista de tareas",
|
||||||
|
size: 14,
|
||||||
|
width: 190,
|
||||||
|
height: 44,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
if (!fullPlan) TextButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.workspace_premium, size: 16),
|
||||||
|
Text("Suscribirme al Plan Completo")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (widget.fullPlan) {
|
||||||
|
if (widget.tasks.isNotEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
border: BoxBorder.all(
|
||||||
|
color: theme.getColorFor(ThemeCode.textPrimary)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundSecondary)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Tareas",
|
||||||
|
style: TextStyle(fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
"Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
...List<Widget>.generate(widget.tasks.length, (int i) =>
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: BoxBorder.fromLTRB(top: BorderSide(
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
width: 8)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Row(children: [
|
||||||
|
Text(
|
||||||
|
"Lista de tareas",
|
||||||
|
style: TextStyle(fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Text(
|
||||||
|
"10 oct - 17 oct",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
...List<CheckboxListTile>.generate(
|
||||||
|
widget.tasks[i].subtasks.length, (int j) =>
|
||||||
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
checkboxScaleFactor: 2,
|
||||||
|
value: widget.tasks[i].subtasks[j].completed,
|
||||||
|
title: Text(widget.tasks[i].subtasks[j].name),
|
||||||
|
controlAffinity: ListTileControlAffinity
|
||||||
|
.leading,
|
||||||
|
activeColor: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary),
|
||||||
|
onChanged: (_) =>
|
||||||
|
setState(() {
|
||||||
|
widget.tasks[i].subtasks[j] = Subtask(
|
||||||
|
name: widget.tasks[i].subtasks[j]
|
||||||
|
.name,
|
||||||
|
completed: !widget.tasks[i]
|
||||||
|
.subtasks[j].completed);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStatePropertyAll(EdgeInsets
|
||||||
|
.zero)),
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
"+ Añadir tarea",
|
||||||
|
style: TextStyle(fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode
|
||||||
|
.backgroundTertiary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(
|
||||||
|
24))
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(child: Text(
|
||||||
|
"Recompensa por cumplimiento",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16, letterSpacing: 0),
|
||||||
|
)),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.emoji_events_outlined, size: 16,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary)),
|
||||||
|
MoneyText(
|
||||||
|
text: "${widget.tasks[i]
|
||||||
|
.rewardAmount}€",
|
||||||
|
size: 40,
|
||||||
|
secondarySize: 24,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.buttonPrimary)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
"Este es el mensaje que se enviará al cumplir con todas las tareas:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(
|
||||||
|
"¡Enhorabuena, has cumplido todas las tareas de esta semana!",
|
||||||
|
style: TextStyle(fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.delete_outline_outlined,
|
||||||
|
size: 24),
|
||||||
|
Text(
|
||||||
|
"Eliminar lista de tareas",
|
||||||
|
style: TextStyle(fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
letterSpacing: 0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SecondaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Crear una lista de tareas nueva",
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return emptyBlock(fullPlan: true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return emptyBlock(fullPlan: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,31 @@
|
|||||||
import 'package:auth/auth.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:home/src/presentation/deposit_screen.dart';
|
import 'package:home/src/presentation/wallet_item.dart';
|
||||||
import 'package:home/src/presentation/kid_wallet_screen.dart';
|
|
||||||
import 'package:home/src/presentation/money_text.dart';
|
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:navigation/navigation.dart';
|
||||||
|
|
||||||
class HomeScreen extends ConsumerWidget {
|
class HomeScreen extends ConsumerWidget {
|
||||||
final String name = "Juan";
|
final String name = "Juan";
|
||||||
final double total = 95.03;
|
final double total = 95.03;
|
||||||
final List<Kid> kids = [
|
final List<Kid> kids = [
|
||||||
Kid(name: "Carlos", balance: 25.47),
|
Kid(name: "Carlos", balance: 25.47, savings: 8.32),
|
||||||
Kid(name: "Ana", balance: 25.47),
|
Kid(name: "Ana", balance: 25.47, savings: 8.32),
|
||||||
];
|
];
|
||||||
late final double available = double.parse(
|
late final double available = double.parse(
|
||||||
kids.fold(total, (t, e) => t - e.balance).toStringAsFixed(2),
|
kids.fold(total, (t, e) => t - e.balance).toStringAsFixed(2),
|
||||||
);
|
);
|
||||||
|
late final double savings = double.parse(
|
||||||
|
kids.fold(0.0, (t, e) => t + e.savings).toStringAsFixed(2),
|
||||||
|
);
|
||||||
|
|
||||||
HomeScreen({super.key});
|
HomeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themePortProvider);
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -41,7 +43,7 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
children: <TextSpan>[
|
children: <TextSpan>[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: name,
|
text: name,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -51,10 +53,7 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => Navigator.push(
|
onPressed: () => navigationContract.pushTo(AppRoutes.deviceSignup),
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => CreateProfileScreen()),
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
"+ Añadir otro peque",
|
"+ Añadir otro peque",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -64,66 +63,7 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
WalletBalanceBlock(max: total, value: total - available, savings: savings),
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
spacing: 5,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Wallet",
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
MoneyText(
|
|
||||||
text: "$total€ total",
|
|
||||||
size: 25,
|
|
||||||
resize: true,
|
|
||||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
LinearProgressIndicator(
|
|
||||||
value: available / total,
|
|
||||||
minHeight: 70,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
|
||||||
backgroundColor: theme.getColorFor(
|
|
||||||
ThemeCode.backgroundTertiary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FractionallySizedBox(
|
|
||||||
widthFactor: available / total,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 10),
|
|
||||||
child: Center(
|
|
||||||
child: MoneyText(
|
|
||||||
text: "$available€",
|
|
||||||
size: 35,
|
|
||||||
resize: true,
|
|
||||||
color: theme.getColorFor(
|
|
||||||
ThemeCode.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Center(child: Text("Disponible")),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DepositBlock(max: 150 - total),
|
DepositBlock(max: 150 - total),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -133,141 +73,11 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget walletsList(BuildContext context, List<Kid> kids, WidgetRef ref) {
|
Widget walletsList(BuildContext context, List<Kid> kids, WidgetRef ref) {
|
||||||
final theme = ref.watch(themePortProvider);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: List<Widget>.generate(kids.length, (int index) {
|
children: List<Widget>.generate(kids.length, (int index) {
|
||||||
return GestureDetector(
|
return WalletItem(context, kids[index], index);
|
||||||
onTap: () => {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => KidWalletScreen(kid: kids[index]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: theme.getCardColorFor(index),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(
|
|
||||||
kids[index].name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 25,
|
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 60,
|
|
||||||
width: 60,
|
|
||||||
child: SvgPicture.asset("assets/images/ui/face.svg"),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
MoneyText(
|
|
||||||
text: "${kids[index].balance}€",
|
|
||||||
size: 50,
|
|
||||||
resize: true,
|
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"en su hucha",
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => Dialog(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 100,
|
|
||||||
width: double.infinity,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text("Cámara"),
|
|
||||||
),
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => {},
|
|
||||||
child: Text("Galería de fotos"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Editar",
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.getColorFor(
|
|
||||||
ThemeCode.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => DepositScreen(kid: kids[index]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
|
||||||
theme.getColorFor(ThemeCode.buttonSecondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 0,
|
|
||||||
vertical: 10,
|
|
||||||
),
|
|
||||||
child: Text("+ Añadir dinero"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:home/src/presentation/deposit_screen.dart';
|
import 'package:home/src/presentation/deposit_screen.dart';
|
||||||
|
import 'package:home/src/presentation/extract_screen.dart';
|
||||||
|
import 'package:home/src/presentation/goals_screen.dart';
|
||||||
|
import 'package:home/src/presentation/lock_card_screen.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:home/src/presentation/limits_screen.dart';
|
import 'package:home/src/presentation/limits_screen.dart';
|
||||||
import 'package:home/src/presentation/money_text.dart';
|
|
||||||
import 'package:home/src/presentation/wage_screen.dart';
|
import 'package:home/src/presentation/wage_screen.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ class KidWalletScreen extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themePortProvider);
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final bool locked = false;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
|
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
@@ -23,19 +26,19 @@ class KidWalletScreen extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
borderRadius: const BorderRadius.only(bottomRight: Radius.circular(24), bottomLeft: Radius.circular(24)),
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: theme.getCardColorFor(0),
|
colors: locked? theme.getDisabledCardColors() : theme.getCardColorFor(0)
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
child: SizedBox(width: double.infinity, height: 300),
|
child: SizedBox(width: double.infinity, height: 420)
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.symmetric(vertical: 50, horizontal: 20),
|
margin: EdgeInsets.symmetric(vertical: 50, horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 15,
|
spacing: 24,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 7,
|
spacing: 7,
|
||||||
@@ -45,191 +48,257 @@ class KidWalletScreen extends ConsumerWidget {
|
|||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back_ios_new_outlined,
|
Icons.arrow_back_ios_new_outlined,
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
),
|
size: 24
|
||||||
|
)
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
child: SvgPicture.asset("assets/images/ui/face.svg"),
|
child: SvgPicture.asset("assets/images/ui/face.svg")
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
kid.name,
|
kid.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 20,
|
fontSize: 20
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 30,
|
height: 30,
|
||||||
child: SvgPicture.asset("assets/images/ui/face.svg"),
|
child: SvgPicture.asset("assets/images/ui/face.svg")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
MoneyText(
|
||||||
|
text: "${kid.balance.toString()}€",
|
||||||
|
size: 60,
|
||||||
|
secondarySize: 24,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary)
|
||||||
),
|
),
|
||||||
],
|
Text(
|
||||||
),
|
"Saldo disponible",
|
||||||
MoneyText(
|
style: TextStyle(
|
||||||
text: "${kid.balance.toString()}€",
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
size: 60,
|
)
|
||||||
resize: true,
|
),
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
LinearProgressIndicator(
|
||||||
),
|
value: 0.7,
|
||||||
Text(
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
"Saldo disponible",
|
backgroundColor: theme
|
||||||
style: TextStyle(
|
.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
.withAlpha(0x4C),
|
||||||
),
|
minHeight: 10,
|
||||||
),
|
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||||
LinearProgressIndicator(
|
),
|
||||||
value: 0.7,
|
TextButton(
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
backgroundColor: theme
|
onPressed: () => Navigator.push(
|
||||||
.getColorFor(ThemeCode.backgroundPrimary)
|
context,
|
||||||
.withAlpha(0x4C),
|
MaterialPageRoute(
|
||||||
minHeight: 10,
|
builder: (_) => LockCardScreen(kid: kid)
|
||||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
)
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
margin: EdgeInsets.only(top: 30),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
),
|
|
||||||
child: Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
Icon(Icons.lock_outline, size: 24, color: theme.getColorFor(ThemeCode.textSecondary)),
|
||||||
onPressed: () => Navigator.push(
|
locked ?
|
||||||
context,
|
Text("Desbloquear tarjeta", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary))) :
|
||||||
MaterialPageRoute(
|
Text("Bloquear tarjeta", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary)))
|
||||||
builder: (_) => DepositScreen(kid: kid),
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
margin: EdgeInsets.only(top: 30),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20))
|
||||||
|
),
|
||||||
|
child: Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => DepositScreen(kid: kid)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.add_circle_outline,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Añadir",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
Spacer(),
|
||||||
child: Column(
|
TextButton(
|
||||||
spacing: 10,
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
children: [
|
onPressed: () => Navigator.push(
|
||||||
Icon(
|
context,
|
||||||
Icons.add_circle_outline,
|
MaterialPageRoute(
|
||||||
color: theme.getColorFor(
|
builder: (_) => WageScreen(kid: kid),
|
||||||
ThemeCode.textPrimary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
child: Column(
|
||||||
"Añadir",
|
spacing: 10,
|
||||||
style: TextStyle(
|
children: [
|
||||||
color: theme.getColorFor(
|
Icon(
|
||||||
ThemeCode.textPrimary,
|
Icons.account_balance_wallet_outlined,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
|
"Paga",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => WageScreen(kid: kid),
|
|
||||||
),
|
),
|
||||||
),
|
Spacer(),
|
||||||
child: Column(
|
TextButton(
|
||||||
spacing: 10,
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
children: [
|
onPressed: () => Navigator.push(
|
||||||
Icon(
|
context,
|
||||||
Icons.account_balance_wallet_outlined,
|
MaterialPageRoute(
|
||||||
color: theme.getColorFor(
|
builder: (_) => LimitsScreen(kid: kid),
|
||||||
ThemeCode.textPrimary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
child: Column(
|
||||||
"Paga",
|
spacing: 10,
|
||||||
style: TextStyle(
|
children: [
|
||||||
color: theme.getColorFor(
|
Icon(
|
||||||
ThemeCode.textPrimary,
|
Icons.list_alt_outlined,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
|
"Límites",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => LimitsScreen(kid: kid),
|
|
||||||
),
|
),
|
||||||
),
|
Spacer(),
|
||||||
child: Column(
|
TextButton(
|
||||||
spacing: 10,
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
children: [
|
onPressed: () => Navigator.push(
|
||||||
Icon(
|
context,
|
||||||
Icons.list_alt_outlined,
|
MaterialPageRoute(
|
||||||
color: theme.getColorFor(
|
builder: (_) => GoalsScreen(kid: kid)
|
||||||
ThemeCode.textPrimary,
|
)
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
child: Column(
|
||||||
"Límites",
|
spacing: 10,
|
||||||
style: TextStyle(
|
children: [
|
||||||
color: theme.getColorFor(
|
Icon(
|
||||||
ThemeCode.textPrimary,
|
Icons.emoji_events_outlined,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
),
|
"Metas",
|
||||||
],
|
style: TextStyle(
|
||||||
),
|
color: theme.getColorFor(
|
||||||
),
|
ThemeCode.textPrimary,
|
||||||
Spacer(),
|
),
|
||||||
TextButton(
|
),
|
||||||
onPressed: () => {},
|
|
||||||
child: Column(
|
|
||||||
spacing: 10,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.emoji_events_outlined,
|
|
||||||
color: theme.getColorFor(
|
|
||||||
ThemeCode.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Metas",
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.getColorFor(
|
|
||||||
ThemeCode.textPrimary,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
Spacer(),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => ExtractScreen(kid: kid)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.cancel_outlined,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Quitar",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
),
|
padding: EdgeInsets.all(15),
|
||||||
Container(
|
height: 300,
|
||||||
padding: EdgeInsets.all(15),
|
decoration: BoxDecoration(
|
||||||
height: 400,
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.all(Radius.circular(20))
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
child: Expanded(child: Column(
|
||||||
),
|
//spacing: 24,
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Text("Últimos movimientos", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
|
||||||
Text("Últimos movimientos"),
|
activityList(context, theme)
|
||||||
activityList(context, theme),
|
]
|
||||||
TextButton(onPressed: () => {}, child: Text("Ver todos")),
|
))
|
||||||
],
|
)
|
||||||
),
|
]
|
||||||
),
|
)
|
||||||
],
|
]
|
||||||
),
|
)
|
||||||
),
|
)
|
||||||
],
|
]
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,58 +320,62 @@ class KidWalletScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: List<Widget>.generate(activity.length, (int index) {
|
padding: EdgeInsets.symmetric(vertical: 0),
|
||||||
return Column(
|
children: [
|
||||||
spacing: 20,
|
...List<Widget>.generate(activity.length, (int index) {
|
||||||
children: [
|
return Container(padding: EdgeInsets.only(top: 24),
|
||||||
Text(activity[index]["date"].toString()),
|
child: Column(
|
||||||
Column(
|
spacing: 8,
|
||||||
spacing: 15,
|
children: [
|
||||||
children: List<Widget>.generate(
|
Align(alignment: Alignment.bottomLeft, child: Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18, height: 1.5))),
|
||||||
(activity[index]["payments"] as List<Object>).length,
|
Column(
|
||||||
(int i) {
|
spacing: 16,
|
||||||
//var a = (activity[index]["payments"] as List<Object>)[i];
|
children: List<Widget>.generate(
|
||||||
return Row(
|
(activity[index]["payments"] as List<Object>).length,
|
||||||
spacing: 7,
|
(int i) {
|
||||||
children: [
|
return Row(
|
||||||
Container(
|
spacing: 12,
|
||||||
padding: EdgeInsets.all(9),
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
color: theme.getColorFor(
|
padding: EdgeInsets.all(9),
|
||||||
ThemeCode.backgroundTertiary,
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.backgroundTertiary,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16))
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
child: Icon(
|
||||||
|
Icons.local_pizza_outlined,
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
child: Icon(
|
Column(
|
||||||
Icons.local_pizza_outlined,
|
children: [
|
||||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
Text(
|
||||||
|
"Vips",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)
|
||||||
|
),
|
||||||
|
Text("20:15")
|
||||||
|
]
|
||||||
),
|
),
|
||||||
),
|
Spacer(),
|
||||||
Column(
|
MoneyText(
|
||||||
children: [
|
text: "5.1€",
|
||||||
Text(
|
size: 16,
|
||||||
"Vips",
|
secondarySize: 12,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||||
),
|
)
|
||||||
Text("20:15"),
|
]
|
||||||
],
|
);
|
||||||
),
|
}
|
||||||
Spacer(),
|
)
|
||||||
MoneyText(
|
)
|
||||||
text: "5.1€",
|
]
|
||||||
size: 20,
|
));
|
||||||
resize: true,
|
}),
|
||||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
TextButton(onPressed: () => {}, child: Align(alignment: Alignment.bottomLeft, child: Text("Ver todos"))),
|
||||||
),
|
]
|
||||||
],
|
)
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
dailyLimits = [
|
dailyLimits = [ //dey, week, month, year
|
||||||
{"title": "Diario L-V", "limit": "5", "edit": false},
|
{"title": "Diario L-V", "limit": "5", "edit": false},
|
||||||
{"title": "Fines de semana", "limit": "8", "edit": false},
|
{"title": "Fines de semana", "limit": "8", "edit": false},
|
||||||
{"title": "Semanal", "limit": "30", "edit": false},
|
{"title": "Semanal", "limit": "30", "edit": false},
|
||||||
@@ -41,14 +41,27 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
"end": "21:00",
|
"end": "21:00",
|
||||||
"edit": false,
|
"edit": false,
|
||||||
},
|
},
|
||||||
{"title": "Vacaciones", "start": "09:00", "end": "22:00", "edit": false},
|
{
|
||||||
|
"title": "Vacaciones",
|
||||||
|
"start": "09:00",
|
||||||
|
"end": "22:00",
|
||||||
|
"edit": false
|
||||||
|
},
|
||||||
];
|
];
|
||||||
conditions = [
|
conditions = [
|
||||||
{"title": "Alimentación", "limit": "10", "edit": false},
|
{"title": "Alimentación", "limit": "10", "active": true, "edit": false},
|
||||||
{"title": "Transporte", "limit": "10", "edit": false},
|
{"title": "Transporte", "limit": "10", "active": false, "edit": false},
|
||||||
{"title": "Alimentación", "limit": "10", "edit": false},
|
{"title": "Alimentación", "limit": "10", "active": false, "edit": false},
|
||||||
|
];
|
||||||
|
blocks = [
|
||||||
|
{"title": "Alojamiento y Hoteles", "active": true},
|
||||||
|
{"title": "Supermercados", "active": true},
|
||||||
|
{"title": "Gasolineras", "active": true},
|
||||||
|
{"title": "Restaurantes", "active": true},
|
||||||
|
{"title": "Bares y discotecas", "active": true},
|
||||||
|
{"title": "Licorerías", "active": true},
|
||||||
|
{"title": "Estancos", "active": true},
|
||||||
];
|
];
|
||||||
blocks = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -57,16 +70,32 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
|
|
||||||
return WalletManagementLayout(
|
return WalletManagementLayout(
|
||||||
kid: widget.kid,
|
kid: widget.kid,
|
||||||
footer: Column(
|
footer: Container(
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
FilledButton(
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
onPressed: () => {},
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24))
|
||||||
child: SizedBox(
|
),
|
||||||
width: double.infinity,
|
padding: EdgeInsets.all(24),
|
||||||
child: Center(child: Text("Guardar límites")),
|
child: Column(
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
text: "Guardar límites",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
),
|
),
|
||||||
),
|
TextButton(
|
||||||
],
|
onPressed: ()=>Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
"Cancelar",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.getColorFor(ThemeCode.textPrimary)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@@ -78,9 +107,12 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Align(
|
||||||
"Pon límite de gastos",
|
alignment: Alignment.topLeft,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
child: Text(
|
||||||
|
"Pon límite de gastos",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
Text("Libertad para ellos, tranquilidad para ti"),
|
Text("Libertad para ellos, tranquilidad para ti"),
|
||||||
...List<Widget>.generate(dailyLimits.length, (int index) {
|
...List<Widget>.generate(dailyLimits.length, (int index) {
|
||||||
@@ -103,7 +135,9 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (dailyLimits[index]["edit"]) TextField(),
|
if (dailyLimits[index]["edit"]) CustomTextField(
|
||||||
|
hint: "5€",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -136,22 +170,113 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
setState(() {
|
setState(() {
|
||||||
timeLimits[index]["edit"] =
|
timeLimits[index]["edit"] = !timeLimits[index]["edit"];
|
||||||
!timeLimits[index]["edit"];
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
child: Text("Editar"),
|
child: Text("Editar"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (timeLimits[index]["edit"]) TextField(),
|
if (timeLimits[index]["edit"]) CustomTextField(
|
||||||
|
hint: "5€",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Condiciones",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: List<Widget>.generate(conditions.length, (int index)=>
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Row(children: [
|
||||||
|
Expanded(child: CheckboxListTile(
|
||||||
|
value: conditions[index]["active"],
|
||||||
|
onChanged: (_)=>setState(() {
|
||||||
|
conditions[index]["active"] = !conditions[index]["active"];
|
||||||
|
}),
|
||||||
|
title: Text(
|
||||||
|
"${conditions[index]["title"]}: ${conditions[index]["limit"]}€/sem",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
checkboxScaleFactor: 2,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: ()=>setState(() {
|
||||||
|
conditions[index]["edit"] = ! conditions[index]["edit"];
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
"Editar",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
if (conditions[index]["edit"]) CustomTextField(
|
||||||
|
hint: "5€",
|
||||||
|
numeric: true,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Comercios bloqueados",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: List<Widget>.generate(blocks.length, (int index) =>
|
||||||
|
CheckboxListTile(
|
||||||
|
value: blocks[index]["active"],
|
||||||
|
onChanged: (_) => setState(() {
|
||||||
|
blocks[index]["active"] = !blocks[index]["active"];
|
||||||
|
}),
|
||||||
|
title: Text(
|
||||||
|
blocks[index]["title"],
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
checkboxScaleFactor: 2,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
105
modules/home/lib/src/presentation/lock_card_screen.dart
Normal file
105
modules/home/lib/src/presentation/lock_card_screen.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class LockCardScreen extends ConsumerWidget{
|
||||||
|
final Kid kid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
LockCardScreen({
|
||||||
|
super.key,
|
||||||
|
required this.kid,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return WalletManagementLayout(
|
||||||
|
kid: kid,
|
||||||
|
children: [Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Bloqueo de tarjeta",
|
||||||
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Este dato aparecerá en el reloj del peque",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0)
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"Este es el mensaje fijado por defecto:",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0)
|
||||||
|
)),
|
||||||
|
Align(alignment: Alignment.topLeft, child: Text(
|
||||||
|
"\"De momento hemos bloqueado el dinero del reloj\"",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Escribir mensaje a ${kid.name} del motivo del bloqueo",
|
||||||
|
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
CustomTextField(
|
||||||
|
hint: "Escribe tu mensaje",
|
||||||
|
lines: 4,
|
||||||
|
length: 150,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 16),
|
||||||
|
Text("Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
footer: Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.only(topRight: Radius.circular(24), topLeft: Radius.circular(24))
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: ()=>{Navigator.pop(context)},
|
||||||
|
text: "Enviar mensaje y bloquear",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: ()=>Navigator.pop(context),
|
||||||
|
child: Text("Cancelar", style: TextStyle(fontSize: 18))
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class MoneyText extends StatelessWidget {
|
|
||||||
final String text;
|
|
||||||
final double size;
|
|
||||||
final bool resize;
|
|
||||||
final Color color;
|
|
||||||
|
|
||||||
const MoneyText({super.key, required this.text, required this.size, required this.resize, required this.color});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final units = text.split(".")[0];
|
|
||||||
final cents = ",${text.split(".")[1]}";
|
|
||||||
|
|
||||||
return Text.rich(TextSpan(
|
|
||||||
text: units,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: size,
|
|
||||||
color: color
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: cents,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: resize ? size/2 : size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:home/src/presentation/wallet_management_layout.dart';
|
import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -41,13 +40,10 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
PrimaryButton(
|
||||||
onPressed: () {},
|
onPressed: () => {},
|
||||||
child: Container(
|
text: "Activar paga automática",
|
||||||
width: double.infinity,
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: const Center(child: Text('Activar paga automática')),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextButton(onPressed: () {}, child: const Text('Cancelar')),
|
TextButton(onPressed: () {}, child: const Text('Cancelar')),
|
||||||
],
|
],
|
||||||
@@ -63,18 +59,16 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Align(alignment: Alignment.topLeft,
|
||||||
'Paga automática',
|
child: const Text(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
"Paga automática",
|
||||||
),
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
|
||||||
TextField(
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Cantidad',
|
|
||||||
hintText: '0€',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
),
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
CustomTextField(
|
||||||
|
numeric: true,
|
||||||
|
label: "Cantidad",
|
||||||
|
hint: "0€",
|
||||||
),
|
),
|
||||||
const Text('Saldo total disponible después: 30 €'),
|
const Text('Saldo total disponible después: 30 €'),
|
||||||
],
|
],
|
||||||
@@ -86,16 +80,21 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Align(alignment: Alignment.topLeft,
|
||||||
'Frecuencia',
|
child: const Text(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
"Frecuencia",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Align(alignment: Alignment.topLeft,
|
||||||
|
child: const Text("Cuándo se envía el dinero"),
|
||||||
),
|
),
|
||||||
const Text('Cuándo se envía el dinero'),
|
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Semanal'),
|
title: const Text('Semanal'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: frequence == 'weekly',
|
value: frequence == 'weekly',
|
||||||
@@ -107,6 +106,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Cada dos semanas'),
|
title: const Text('Cada dos semanas'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: frequence == 'biweekly',
|
value: frequence == 'biweekly',
|
||||||
@@ -118,6 +118,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Mensual'),
|
title: const Text('Mensual'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: frequence == 'monthly',
|
value: frequence == 'monthly',
|
||||||
@@ -128,50 +129,33 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
},
|
},
|
||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
SizedBox(
|
CustomDropdown(
|
||||||
width: double.infinity,
|
items: [
|
||||||
child: DropdownMenu<String>(
|
Text("Lunes"),
|
||||||
label: const Text('Día de la semana'),
|
Text("Martes"),
|
||||||
initialSelection: 'Domingo',
|
Text("Miércoles"),
|
||||||
dropdownMenuEntries: () {
|
Text("Jueves"),
|
||||||
const days = [
|
Text("Viernes"),
|
||||||
'Lunes',
|
Text("Sábado"),
|
||||||
'Martes',
|
Text("Domingo"),
|
||||||
'Miércoles',
|
],
|
||||||
'Jueves',
|
values: ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"],
|
||||||
'Viernes',
|
onChanged: (value)=> {},
|
||||||
'Sábado',
|
hint: "Día de la semana",
|
||||||
'Domingo',
|
|
||||||
];
|
|
||||||
return List<DropdownMenuEntry<String>>.generate(
|
|
||||||
days.length,
|
|
||||||
(index) => DropdownMenuEntry<String>(
|
|
||||||
value: days[index],
|
|
||||||
label: days[index],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
DropdownMenu<int>(
|
CustomDropdown(
|
||||||
label: const Text('Hora del día'),
|
hint: "Hora del día",
|
||||||
initialSelection: 9,
|
items: List<Widget>.generate(24,(int index){
|
||||||
dropdownMenuEntries: List<DropdownMenuEntry<int>>.generate(
|
return Text("$index:00");
|
||||||
24,
|
}),
|
||||||
(index) =>
|
onChanged: (value)=> {},
|
||||||
DropdownMenuEntry<int>(value: index, label: '$index:00'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextField(
|
CustomTextField(
|
||||||
minLines: 3,
|
lines: 3,
|
||||||
maxLines: 3,
|
length: 150,
|
||||||
maxLength: 150,
|
label:
|
||||||
decoration: InputDecoration(
|
"Escribir mensaje a ${widget.kid.name} del motivo del ingreso",
|
||||||
labelText:
|
hint: "Escribe tu mensaje",
|
||||||
'Escribir mensaje a ${widget.kid.name} del motivo del ingreso',
|
|
||||||
hintText: 'Escribe tu mensaje',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
@@ -186,16 +170,21 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Align(alignment: Alignment.topLeft,
|
||||||
'Condiciones',
|
child: Text(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
"Condiciones",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Align(alignment: Alignment.topLeft,
|
||||||
|
child: Text("Este dato aparecerá en el reloj del peque"),
|
||||||
),
|
),
|
||||||
const Text('Este dato aparecerá en el reloj del peque'),
|
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Sólo si cumple límites semanales'),
|
title: const Text('Sólo si cumple límites semanales'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: conditions['weeklyLimits'],
|
value: conditions['weeklyLimits'],
|
||||||
@@ -208,6 +197,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Sólo si no ha tenido incidencias'),
|
title: const Text('Sólo si no ha tenido incidencias'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: conditions['incidences'],
|
value: conditions['incidences'],
|
||||||
@@ -220,6 +210,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
|||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Pausar durante vacaciones'),
|
title: const Text('Pausar durante vacaciones'),
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: conditions['holidays'],
|
value: conditions['holidays'],
|
||||||
|
|||||||
244
modules/home/lib/src/presentation/wallet_item.dart
Normal file
244
modules/home/lib/src/presentation/wallet_item.dart
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:home/src/presentation/deposit_screen.dart';
|
||||||
|
import 'package:home/src/presentation/kid_wallet_screen.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletItem extends ConsumerWidget{
|
||||||
|
final BuildContext context;
|
||||||
|
final Kid kid;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
WalletItem(this.context, this.kid, this.index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref){
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: ()=>{Navigator.push(context, MaterialPageRoute(builder: (_)=>KidWalletScreen(kid: kid)))},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 227,
|
||||||
|
width: 382,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
SizedBox(height:227, width:382, child: CustomPaint(painter: WalletPainter(ref, index))),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(left: 24, right: 24, top: 10, bottom: 0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(kid.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 30,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 70,
|
||||||
|
width: 70,
|
||||||
|
child: SvgPicture.asset("assets/images/ui/face.svg"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Column(children: [
|
||||||
|
MoneyText(
|
||||||
|
text: "${kid.balance}€",
|
||||||
|
size: 50,
|
||||||
|
secondarySize: 30,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary),
|
||||||
|
height: 0
|
||||||
|
),
|
||||||
|
Text("en su hucha", style: TextStyle(fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary), height: 0)),
|
||||||
|
Text("Ahorrado ${kid.savings}€", style: TextStyle(fontSize: 14, color: theme.getColorFor(ThemeCode.textSecondary), height: 0))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Row(children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: ()=>showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => PhotoDialog()
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.edit_outlined, size: 24, color: theme.getColorFor(ThemeCode.textSecondary)),
|
||||||
|
Text("Editar", style: TextStyle(fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary)))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
SizedBox(
|
||||||
|
width: 169,
|
||||||
|
height: 60,
|
||||||
|
child: PrimaryButton(
|
||||||
|
onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))),
|
||||||
|
text: "+ Añadir dinero",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||||
|
radius: 12,
|
||||||
|
)
|
||||||
|
/*FilledButton(
|
||||||
|
onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))),
|
||||||
|
style: ButtonStyle(
|
||||||
|
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
backgroundColor: WidgetStatePropertyAll<Color>(theme.getColorFor(ThemeCode.buttonSecondary)),
|
||||||
|
padding: WidgetStatePropertyAll(EdgeInsets.all(0))
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"+ Añadir dinero",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)*/
|
||||||
|
)
|
||||||
|
])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhotoDialog extends ConsumerWidget{
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 247,
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(16), topLeft: Radius.circular(16)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: ()=>{},
|
||||||
|
text: "Cámara",
|
||||||
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
|
),
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"Galería de fotos",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: theme.getColorFor(
|
||||||
|
ThemeCode.textPrimary)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => {Navigator.pop(context)},
|
||||||
|
child: Text(
|
||||||
|
"Cancelar",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 16
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WalletPainter extends CustomPainter {
|
||||||
|
|
||||||
|
final WidgetRef ref;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
WalletPainter(this.ref, this.index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final gradient = theme.getCardColorFor(index);
|
||||||
|
|
||||||
|
final brush = Paint()
|
||||||
|
..shader = ui.Gradient.linear(
|
||||||
|
Offset(0, 0),
|
||||||
|
Offset(size.width, size.height),
|
||||||
|
gradient,
|
||||||
|
List<double>.generate(gradient.length, (int index){
|
||||||
|
return index/(gradient.length-1);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
..strokeWidth = 3
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
|
final double radius = 20;
|
||||||
|
final double buttonHeight = 60;
|
||||||
|
final double buttonWidth = 169;
|
||||||
|
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(radius, 0)
|
||||||
|
..lineTo(size.width-radius, 0)
|
||||||
|
..arcToPoint(Offset(size.width, radius), radius: Radius.circular(radius))
|
||||||
|
..lineTo(size.width, size.height-buttonHeight-1.5*radius)
|
||||||
|
..arcToPoint(Offset(size.width-radius, size.height-buttonHeight-0.5*radius), radius: Radius.circular(radius))
|
||||||
|
..lineTo(size.width-buttonWidth+0.5*radius, size.height-buttonHeight-0.5*radius)
|
||||||
|
..arcToPoint(Offset(size.width-buttonWidth-0.5*radius, size.height-buttonHeight+0.5*radius), radius: Radius.circular(radius), clockwise: false)
|
||||||
|
..lineTo(size.width-buttonWidth-0.5*radius, size.height-radius)
|
||||||
|
..arcToPoint(Offset(size.width-buttonWidth-1.5*radius, size.height), radius: Radius.circular(radius))
|
||||||
|
..lineTo(radius, size.height)
|
||||||
|
..arcToPoint(Offset(0, size.height-radius), radius: Radius.circular(radius))
|
||||||
|
..lineTo(0, radius)
|
||||||
|
..arcToPoint(Offset(radius, 0), radius: Radius.circular(radius))
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(path, brush);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ class WalletManagementLayout extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themePortProvider);
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
final bool locked = false;
|
||||||
|
|
||||||
final content = [
|
final content = [
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||||
@@ -82,11 +84,11 @@ class WalletManagementLayout extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: theme.getCardColorFor(0),
|
colors: locked? theme.getDisabledCardColors() : theme.getCardColorFor(0)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SizedBox(width: double.infinity, height: 200),
|
child: SizedBox(width: double.infinity, height: 200),
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ dependencies:
|
|||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
sf_shared:
|
sf_shared:
|
||||||
path: ../../packages/sf_shared
|
path: ../../packages/sf_shared
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
#dependencies go here
|
#dependencies go here
|
||||||
flutter_svg: ^2.2.1
|
flutter_svg: ^2.2.1
|
||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.0.3
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation,utils,sf_localizations,fonts,sf_infrastructure
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
auth:
|
auth:
|
||||||
path: ../auth
|
path: ../auth
|
||||||
@@ -6,12 +6,16 @@ dependency_overrides:
|
|||||||
path: ../dashboard_shell
|
path: ../dashboard_shell
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
navigation:
|
navigation:
|
||||||
path: ../../packages/navigation
|
path: ../../packages/navigation
|
||||||
notifications:
|
notifications:
|
||||||
path: ../notifications
|
path: ../notifications
|
||||||
profile:
|
profile:
|
||||||
path: ../profile
|
path: ../profile
|
||||||
|
sf_infrastructure:
|
||||||
|
path: ../../packages/sf_infrastructure
|
||||||
sf_localizations:
|
sf_localizations:
|
||||||
path: ../../packages/sf_localizations
|
path: ../../packages/sf_localizations
|
||||||
sf_shared:
|
sf_shared:
|
||||||
|
|||||||
@@ -47,46 +47,47 @@ class ActivityListState extends ConsumerState<ActivityList> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
spacing: 20,
|
spacing: 32,
|
||||||
children: List<Widget>.generate(widget.activity.length, (int index) {
|
children: List<Widget>.generate(widget.activity.length, (int index) {
|
||||||
final color = colors[index % colors.length];
|
final color = colors[index % colors.length];
|
||||||
final type = widget.activity[index]["type"] as String;
|
final type = widget.activity[index]["type"] as String;
|
||||||
|
|
||||||
final logItem = Container(
|
final logItem = Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
border: Border(left: BorderSide(color: color, width: 5)),
|
border: Border(left: BorderSide(color: color, width: 8)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 15,
|
spacing: 24,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Icon(icons[type], color: color),
|
Icon(icons[type], color: color),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Expanded(child: Text(
|
||||||
titles[type]!,
|
titles[type]!,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
const Spacer(),
|
|
||||||
const Text("14/01/2005"),
|
const Text("14/01/2005"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: Text("Ana ya tiene su paga de 5€ en el reloj"),
|
child: Text("Ana ya tiene su paga de 5€ en el reloj", style: TextStyle(fontSize: 14, letterSpacing: 0)),
|
||||||
),
|
)
|
||||||
],
|
]
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.edit) {
|
if (widget.edit) {
|
||||||
return Row(
|
return Row(
|
||||||
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: values[index],
|
value: values[index],
|
||||||
@@ -96,15 +97,15 @@ class ActivityListState extends ConsumerState<ActivityList> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||||
semanticLabel: "Eliminar",
|
semanticLabel: "Eliminar"
|
||||||
),
|
),
|
||||||
Expanded(child: logItem),
|
Expanded(child: logItem)
|
||||||
],
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return logItem;
|
return logItem;
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ class ActivityScreen extends ConsumerWidget {
|
|||||||
final content = [
|
final content = [
|
||||||
Text(
|
Text(
|
||||||
"Movimientos recientes",
|
"Movimientos recientes",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 24),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
@@ -32,7 +32,7 @@ class ActivityScreen extends ConsumerWidget {
|
|||||||
TextButton(onPressed: () => {}, child: Text("Mes")),
|
TextButton(onPressed: () => {}, child: Text("Mes")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 200, child: LineGraph()),
|
LineGraph(),
|
||||||
ActivityList(activity: activity, edit: false),
|
ActivityList(activity: activity, edit: false),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -54,4 +54,4 @@ class ActivityScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,25 +29,37 @@ class AlertScreenState extends ConsumerState<AlertScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = ref.watch(themePortProvider);
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
|
child: Container(
|
||||||
body: Container(
|
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||||
margin: EdgeInsets.all(30),
|
margin: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text("Alertas"),
|
Text(
|
||||||
|
"Alertas",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
letterSpacing: 0,
|
||||||
|
fontWeight: FontWeight.w500
|
||||||
|
)
|
||||||
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => setState(() {
|
onPressed: () => setState(() {
|
||||||
edit = !edit;
|
edit = !edit;
|
||||||
}),
|
}),
|
||||||
child: Text("Editar"),
|
child: Text("Editar", style: TextStyle(fontSize: 16, letterSpacing: 0)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ActivityList(activity: activity, edit: edit),
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: ActivityList(activity: activity, edit: edit)
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# melos_managed_dependency_overrides: design_system,sf_shared,utils
|
# melos_managed_dependency_overrides: design_system,sf_shared,utils,fonts
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
|
fonts:
|
||||||
|
path: ../../packages/fonts
|
||||||
sf_shared:
|
sf_shared:
|
||||||
path: ../../packages/sf_shared
|
path: ../../packages/sf_shared
|
||||||
utils:
|
utils:
|
||||||
|
|||||||
178
modules/profile/lib/src/core/kid_line_chart.dart
Normal file
178
modules/profile/lib/src/core/kid_line_chart.dart
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class KidLineChart extends ConsumerWidget{
|
||||||
|
final int index;
|
||||||
|
final Kid kid;
|
||||||
|
|
||||||
|
final weekDays = const ["L", "M", "X", "J", "V", "S", "D"];
|
||||||
|
|
||||||
|
const KidLineChart({
|
||||||
|
super.key,
|
||||||
|
required this.kid,
|
||||||
|
this.index = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: 183,
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(colors: theme.getCardColorFor(index)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
child: SvgPicture.asset("assets/images/ui/face.svg"),
|
||||||
|
),
|
||||||
|
Column(children: [
|
||||||
|
Text(
|
||||||
|
kid.name,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
MoneyText(
|
||||||
|
text: "${kid.balance}€",
|
||||||
|
size: 26,
|
||||||
|
secondarySize: 16,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 93,
|
||||||
|
child: LineChart(LineChartData(
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawHorizontalLine: false,
|
||||||
|
drawVerticalLine: true,
|
||||||
|
verticalInterval: 1,
|
||||||
|
getDrawingVerticalLine: (value)=>FlLine(strokeWidth: 12, color: Colors.black26)
|
||||||
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
interval: 1,
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 29,
|
||||||
|
getTitlesWidget: (double value, TitleMeta meta){
|
||||||
|
String text = weekDays[value.toInt()];
|
||||||
|
|
||||||
|
return SideTitleWidget(
|
||||||
|
space: 4,
|
||||||
|
meta: meta,
|
||||||
|
child: Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(),
|
||||||
|
topTitles: AxisTitles(),
|
||||||
|
rightTitles: AxisTitles()
|
||||||
|
),
|
||||||
|
lineTouchData: LineTouchData(
|
||||||
|
touchTooltipData: LineTouchTooltipData(
|
||||||
|
tooltipBorderRadius: BorderRadius.all(Radius.circular(7)),
|
||||||
|
getTooltipColor: (spot) => theme.getColorFor(ThemeCode.buttonSecondary),
|
||||||
|
getTooltipItems: (List<LineBarSpot> touchedSpots) {
|
||||||
|
return touchedSpots.map((LineBarSpot touchedSpot) {
|
||||||
|
return LineTooltipItem(
|
||||||
|
"${touchedSpot.y}€",
|
||||||
|
TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.getColorFor(ThemeCode.textSecondary)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
getTouchedSpotIndicator: (
|
||||||
|
_,
|
||||||
|
indicators,
|
||||||
|
) {
|
||||||
|
return indicators
|
||||||
|
.map((int index) => const TouchedSpotIndicatorData(
|
||||||
|
FlLine(color: Colors.transparent),
|
||||||
|
FlDotData(show: false),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
touchSpotThreshold: 25,
|
||||||
|
distanceCalculator:
|
||||||
|
(Offset touchPoint, Offset spotPixelCoordinates) =>
|
||||||
|
(touchPoint - spotPixelCoordinates).distance,
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(
|
||||||
|
show: true,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Colors.lightBlue.withValues(alpha: 0.2),
|
||||||
|
width: 4
|
||||||
|
),
|
||||||
|
left: const BorderSide(color: Colors.transparent),
|
||||||
|
right: const BorderSide(color: Colors.transparent),
|
||||||
|
top: const BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
lineBarsData: [
|
||||||
|
LineChartBarData(
|
||||||
|
isCurved: true,
|
||||||
|
color: Colors.white,
|
||||||
|
barWidth: 4,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
spots: List<FlSpot>.generate(weekDays.length, (int index){
|
||||||
|
return FlSpot(index.toDouble(), ((index+1)%2)/2+0.25);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
],
|
||||||
|
minX: 0,
|
||||||
|
maxX: weekDays.length-1,
|
||||||
|
maxY: 1,
|
||||||
|
minY: 0,
|
||||||
|
))
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Text("Editar hucha", style: TextStyle(color: theme.getColorFor(ThemeCode.textSecondary), fontSize: 14)),
|
||||||
|
Icon(Icons.edit_outlined, color: theme.getColorFor(ThemeCode.textSecondary), size: 13)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:notifications/notifications.dart';
|
import 'package:notifications/notifications.dart';
|
||||||
|
import 'package:profile/src/core/kid_line_chart.dart';
|
||||||
import 'package:profile/src/settings_screen.dart';
|
import 'package:profile/src/settings_screen.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -19,9 +20,16 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
{"type": "lock"},
|
{"type": "lock"},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final kids = [
|
||||||
|
Kid(name: "Ana", balance: 15, savings: 5),
|
||||||
|
Kid(name: "Carlos", balance: 15, savings: 5)
|
||||||
|
];
|
||||||
|
|
||||||
final name = "Juan";
|
final name = "Juan";
|
||||||
final total = 95.03;
|
final total = 95.03;
|
||||||
final available = 44.09;
|
final available = 44.09;
|
||||||
|
final savings = 4.16;
|
||||||
|
final savingsPlan = 30.0;
|
||||||
|
|
||||||
final content = [
|
final content = [
|
||||||
Row(
|
Row(
|
||||||
@@ -49,53 +57,45 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
WalletBalanceBlock(max: total, value: available, savings: savings, savingsPlan: savingsPlan),
|
||||||
|
LineGraph(),
|
||||||
|
DepositBlock(max: 150 - total),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.all(20),
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: TextButton(
|
||||||
spacing: 5,
|
onPressed: ()=>{},
|
||||||
children: [
|
child: Row(
|
||||||
Row(
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
Text("Wallet", style: TextStyle(fontWeight: FontWeight.bold)),
|
Icon(Icons.output_outlined,
|
||||||
Spacer(),
|
size: 24,
|
||||||
Text("$total€"),
|
color: theme.getColorFor(ThemeCode.textPrimary)
|
||||||
],
|
),
|
||||||
),
|
Text(
|
||||||
Stack(
|
"Retirar dinero del wallet",
|
||||||
children: [
|
style: TextStyle(
|
||||||
LinearProgressIndicator(
|
fontSize: 20,
|
||||||
value: available / total,
|
fontWeight: FontWeight.w500,
|
||||||
minHeight: 70,
|
color: theme.getColorFor(ThemeCode.textPrimary)
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
)
|
||||||
),
|
)
|
||||||
FractionallySizedBox(
|
],
|
||||||
widthFactor: available / total,
|
)
|
||||||
child: Container(
|
)
|
||||||
padding: EdgeInsets.symmetric(vertical: 20),
|
),
|
||||||
child: Center(
|
SingleChildScrollView(
|
||||||
child: Text(
|
scrollDirection: Axis.horizontal,
|
||||||
"$available€",
|
child: Row(
|
||||||
style: TextStyle(
|
spacing: 16,
|
||||||
color: theme.getColorFor(ThemeCode.textSecondary),
|
children: List<Widget>.generate(kids.length, (int index){
|
||||||
fontSize: 20,
|
return KidLineChart(kid: kids[index], index: index);
|
||||||
),
|
})
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Center(child: Text("Disponible")),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 200, child: LineGraph()),
|
|
||||||
DepositBlock(max: 150 - total),
|
|
||||||
Row(),
|
|
||||||
ActivityList(activity: activity, edit: false),
|
ActivityList(activity: activity, edit: false),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
borderRadius: const BorderRadius.only(bottomRight: Radius.circular(24), bottomLeft: Radius.circular(24)),
|
||||||
color: Color(0xFF4B4B4B),
|
color: Color(0xFF4B4B4B),
|
||||||
),
|
),
|
||||||
child: SizedBox(width: double.infinity, height: 200),
|
child: SizedBox(width: double.infinity, height: 200),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -57,15 +57,15 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
TextButton(onPressed: () => {}, child: Text("Editar wallet")),
|
TextButton(onPressed: () => {}, child: Text("Editar wallet")),
|
||||||
Icon(Icons.attach_money),
|
Icon(Icons.account_balance, size: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(relation),
|
Align(alignment: Alignment.centerLeft, child: Text(relation)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -73,7 +73,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Datos personales",
|
"Datos personales",
|
||||||
@@ -83,47 +82,56 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
TextButton(onPressed: () => {}, child: Text("Editar")),
|
TextButton(onPressed: () => {}, child: Text("Editar")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Nombre: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Nombre: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: fullName,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: fullName,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Fecha de nacimiento: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Fecha de nacimiento: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: birthDate,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: birthDate,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Familiar: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Familiar: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: relation,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: relation,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -131,7 +139,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Dirección",
|
"Dirección",
|
||||||
@@ -141,47 +148,56 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
TextButton(onPressed: () => {}, child: Text("Editar")),
|
TextButton(onPressed: () => {}, child: Text("Editar")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Dirección: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Dirección: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: address,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: address,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "País: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "País: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: country,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: country,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Nacionalidad: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Nacionalidad: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: nationality,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: nationality,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -189,7 +205,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Usuario",
|
"Usuario",
|
||||||
@@ -199,42 +214,46 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
TextButton(onPressed: () => {}, child: Text("Editar")),
|
TextButton(onPressed: () => {}, child: Text("Editar")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Correo: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Correo: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: email,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: email,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text.rich(
|
Align(
|
||||||
TextSpan(
|
alignment: Alignment.centerLeft,
|
||||||
text: "Teléfono: ",
|
child: Text.rich(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
children: [
|
text: "Teléfono: ",
|
||||||
TextSpan(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
text: phone,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.normal),
|
TextSpan(
|
||||||
),
|
text: phone,
|
||||||
],
|
style: TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Cambio de contraseña",
|
"Cambio de contraseña",
|
||||||
@@ -246,7 +265,7 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
@@ -254,7 +273,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Método de pago",
|
"Método de pago",
|
||||||
@@ -269,7 +287,26 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
|
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Retirar y reembolsar dinero del wallet",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||||
|
),
|
||||||
|
Text("Para transferirte el saldo a tu cuenta necesitamos tu IBAN y algunos datos básics. Así nos aseguramos de que la transferencia sea segura y rápida."),
|
||||||
|
CustomTextField(label: "Nombre y Apellidos", hint: "******"),
|
||||||
|
CustomTextField(label: "IBAN con número español", hint: "******")
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: theme.getColorFor(ThemeCode.backgroundTertiary),
|
color: theme.getColorFor(ThemeCode.backgroundTertiary),
|
||||||
@@ -277,7 +314,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Plan anual",
|
"Plan anual",
|
||||||
@@ -287,15 +323,81 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
TextButton(onPressed: () => {}, child: Text("Cambiar Plan")),
|
TextButton(onPressed: () => {}, child: Text("Cambiar Plan")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text("Sin permanencia"),
|
Align(
|
||||||
Text("Llamadas y datos ilimitados"),
|
alignment: Alignment.centerLeft,
|
||||||
Text("2 meses gratis"),
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.check, size: 24),
|
||||||
|
Text(
|
||||||
|
"Sin permanencia",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.check, size: 24),
|
||||||
|
Text(
|
||||||
|
"Llamadas y datos ilimitados",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.check, size: 24),
|
||||||
|
Text(
|
||||||
|
"2 meses gratis",
|
||||||
|
style: TextStyle(fontSize: 16, letterSpacing: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
TextButton(onPressed: () => {}, child: Text("Contáctanos")),
|
spacing: 16,
|
||||||
TextButton(onPressed: () => {}, child: Text("Preguntas frecuentes")),
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.contact_support_outlined, size: 24),
|
||||||
|
Text("Contáctanos")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
|
||||||
|
onPressed: () => {},
|
||||||
|
child: Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.contact_support_outlined, size: 24),
|
||||||
|
Text("Preguntas frecuentes")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -304,7 +406,7 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||||
color: Color(0xFF4B4B4B),
|
color: Color(0xFF4B4B4B),
|
||||||
),
|
),
|
||||||
child: SizedBox(width: double.infinity, height: 200),
|
child: SizedBox(width: double.infinity, height: 200),
|
||||||
@@ -336,13 +438,10 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
PrimaryButton(
|
||||||
onPressed: () => {},
|
onPressed: () => {},
|
||||||
child: Container(
|
text: "Guardar cambios",
|
||||||
width: double.infinity,
|
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
child: Center(child: Text("Guardar cambios")),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ dependencies:
|
|||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.0.3
|
||||||
get_it: ^9.0.5
|
get_it: ^9.0.5
|
||||||
go_router: ^17.0.0
|
go_router: ^17.0.0
|
||||||
|
flutter_svg: ^2.2.2
|
||||||
|
fl_chart: ^1.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user