Compare commits
171 Commits
v1.0.0(7)
...
6ed36dba75
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ed36dba75 | |||
| 4deb263c7e | |||
| 82786b3577 | |||
| ff48b873e9 | |||
| 35948998f6 | |||
| c034d781af | |||
| 5bebe110fc | |||
| 107a4ec593 | |||
| 03effaed13 | |||
| 32eb4e0d52 | |||
| 6f5855e2fd | |||
| 412cb96888 | |||
| 853b6f20a3 | |||
| 01cb4c9427 | |||
| 82123a6d5f | |||
| 3956a87862 | |||
| 7251349e1d | |||
| cf2dbbeb63 | |||
| e9cceae485 | |||
| 6de01b62ae | |||
| 7c7ffb8f3d | |||
| 80f95bae5a | |||
| 63547b0f37 | |||
| 9ab78ac965 | |||
| 375e613caf | |||
| 9b253dd545 | |||
| 3f3fb3d5d0 | |||
| e48dec979c | |||
| 1c30318e06 | |||
| d5d38637a7 | |||
| ac5219f389 | |||
| 4fbdce3c8c | |||
| 5ad0a7acc5 | |||
| 065433ff61 | |||
| c06fb06d03 | |||
| 04c26e83cf | |||
| dc7325ea65 | |||
| 76782fbfd4 | |||
| c17e94ff7f | |||
| c84287e803 | |||
| 44c8949c07 | |||
| aaecc38461 | |||
| 3470e1bfef | |||
| 0530f892f2 | |||
| 734bd79af7 | |||
| 94e2fcbf7d | |||
| 35a943c066 | |||
| 5193e6ada2 | |||
| 2052fdcf85 | |||
| 4e50384dd9 | |||
| 9f5ec3f1da | |||
| db3197a93a | |||
| b90eed2a54 | |||
| 118be4c6c0 | |||
| 62de343dae | |||
| df92c51344 | |||
| 221d053d5f | |||
| e5cf5fcb61 | |||
| 3e427f44d7 | |||
| 746230a541 | |||
| 86642b9587 | |||
| 71ffc52993 | |||
| d355ee2442 | |||
| cc5159fc56 | |||
| d6d82d20c6 | |||
| f2d2385f24 | |||
| e6974c7be7 | |||
| 20cebc8bc7 | |||
| 2247833203 | |||
| 92e93a2b69 | |||
| 691dfc0472 | |||
| 2b9b6aa215 | |||
| 4cd4be24e6 | |||
| a547f7a786 | |||
| 42698631a3 | |||
| 69fdc2233f | |||
| 75b47e2c25 | |||
| 1c0a8b7bb7 | |||
| 417b6660fc | |||
| b8ac786146 | |||
| dd1617939b | |||
| 4c85af38aa | |||
| 309ff8b8b7 | |||
| e040944965 | |||
| b6526f20ee | |||
| 0418f16f87 | |||
| f36ad5e4a6 | |||
| 0a50941c2b | |||
| 7746d08759 | |||
| 72c88cc4b0 | |||
| b21b234b9a | |||
| f89bca99b3 | |||
| 1056895c31 | |||
| 424b8d9034 | |||
| 4aa91c355e | |||
| 7ea415cb6e | |||
| 21fd1e0197 | |||
| d618ed76d0 | |||
| dfd7ba9c41 | |||
| b8f5c5d6f8 | |||
| d470ed470a | |||
| a400fef77d | |||
| febc21a590 | |||
| 29fca859fc | |||
| d92fe887fd | |||
| 315e5b2908 | |||
| 244e5bbd03 | |||
| a86041885c | |||
| 12011ce525 | |||
| c92e2fb67f | |||
| 7e1ead9cae | |||
| e59ce36033 | |||
| aa3ffdb6a7 | |||
| 2eddb99c47 | |||
| c461519597 | |||
| 919ee55c45 | |||
| f5350f5e78 | |||
| e7ebe7f403 | |||
| ed41b82076 | |||
| 9470f54867 | |||
| f82d222df3 | |||
| fd8ef27185 | |||
| 5b6ed5cf16 | |||
| bf1032245a | |||
| fad2c8792c | |||
| 73d9de45a2 | |||
| 56d89fcdc4 | |||
| eff6f01924 | |||
| 72d44b81df | |||
| 2942d7393e | |||
| 0b160758e2 | |||
| ecbb6d1e76 | |||
| e7a4653c01 | |||
| 05ffe572c8 | |||
| cbc40f7d95 | |||
| 27e26ca921 | |||
| e83adbfdbf | |||
| 973fc2490c | |||
| e148b9fdfa | |||
| 238c15888b | |||
| ddc5086b3b | |||
| 769e8fea27 | |||
| 297fa8241a | |||
| 984a87f200 | |||
| cda889a15b | |||
| 1230a27d94 | |||
| bc46f31434 | |||
| 514daf9c7c | |||
| 51a3979c03 | |||
| c7e32d1399 | |||
| 4e21e8d698 | |||
| 703b1e9fba | |||
| 2fe5a2399d | |||
| 9e41090712 | |||
| 648d0fc04b | |||
| 56e437ff13 | |||
| 88c1111bd5 | |||
| 85be483c4e | |||
| 08e099fc37 | |||
| 8a97304ff5 | |||
| 8c1ca94a08 | |||
| cbaff2e763 | |||
| f36bc9afc1 | |||
| 95a03434ca | |||
| 6b2034612a | |||
| ec14ad49e5 | |||
| 03998f9cf1 | |||
| 811e92defc | |||
| 1e60b38087 | |||
| 693f55369c | |||
| 506dd5a80f |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
# Dart / Flutter workspace caches (regenerated by `flutter pub get` / `dart pub get`)
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
**/.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
@@ -15,7 +15,15 @@
|
||||
**/macos/Flutter/ephemeral/
|
||||
**/windows/flutter/ephemeral/
|
||||
|
||||
# Flutter iOS build config (regenerated on pub get; contains machine-specific paths)
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
.vscode/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
38
.idea/modules.xml
generated
38
.idea/modules.xml
generated
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/account/melos_account.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/account/melos_account.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/payment/modules/activity/melos_activity.iml" filepath="$PROJECT_DIR$/modules/payment/modules/activity/melos_activity.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/payment/modules/auth/melos_auth.iml" filepath="$PROJECT_DIR$/modules/payment/modules/auth/melos_auth.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/control_panel/melos_control_panel.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/control_panel/melos_control_panel.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/customer_service/melos_customer_service.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/customer_service/melos_customer_service.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/payment/modules/dashboard_shell/melos_dashboard_shell.iml" filepath="$PROJECT_DIR$/modules/payment/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$/modules/legacy/modules/device_management/melos_device_management.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/device_management/melos_device_management.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/melos_flutter_treezor_entrust_sdk_bridge.iml" filepath="$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/melos_flutter_treezor_entrust_sdk_bridge.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/payment/modules/home/melos_home.iml" filepath="$PROJECT_DIR$/modules/payment/modules/home/melos_home.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/melos_legacy.iml" filepath="$PROJECT_DIR$/modules/legacy/melos_legacy.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/legacy_auth/melos_legacy_auth.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/legacy_auth/melos_legacy_auth.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/legacy_dashboard_shell/melos_legacy_dashboard_shell.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/legacy_dashboard_shell/melos_legacy_dashboard_shell.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/packages/legacy_shared/melos_legacy_shared.iml" filepath="$PROJECT_DIR$/modules/legacy/packages/legacy_shared/melos_legacy_shared.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/location/melos_location.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/location/melos_location.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/payment/modules/notifications/melos_notifications.iml" filepath="$PROJECT_DIR$/modules/payment/modules/notifications/melos_notifications.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/payments/melos_payments.iml" filepath="$PROJECT_DIR$/packages/payments/melos_payments.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/payment/modules/profile/melos_profile.iml" filepath="$PROJECT_DIR$/modules/payment/modules/profile/melos_profile.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/sca_treezor/melos_sca_treezor.iml" filepath="$PROJECT_DIR$/packages/sca_treezor/melos_sca_treezor.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/legacy/modules/settings/melos_settings.iml" filepath="$PROJECT_DIR$/modules/legacy/modules/settings/melos_settings.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_shared/melos_sf_shared.iml" filepath="$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/splash/melos_splash.iml" filepath="$PROJECT_DIR$/modules/splash/melos_splash.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/utils/melos_utils.iml" filepath="$PROJECT_DIR$/packages/utils/melos_utils.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/melos_sf_app_platform_mono_repo.iml" filepath="$PROJECT_DIR$/melos_sf_app_platform_mono_repo.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/example/melos_flutter_treezor_entrust_sdk_bridge_example.iml" filepath="$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/example/melos_flutter_treezor_entrust_sdk_bridge_example.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/melos_sf-app-platform.iml" filepath="$PROJECT_DIR$/melos_sf-app-platform.iml"/>
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -101,4 +101,10 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
// Required by AntelopAwareMessagingService to forward FCM pushes to the
|
||||
// Antelop SDK. The Antelop AAR (com.entrust.antelop:antelop) is brought
|
||||
// in transitively through the flutter_treezor_entrust_sdk_bridge plugin.
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
|
||||
implementation("com.google.firebase:firebase-messaging")
|
||||
implementation("com.entrust.antelop:antelop:2.6.4")
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="fr.antelop.application_id"
|
||||
android:value="3381448747424346509" />
|
||||
android:value="4713640103500149457" />
|
||||
<meta-data
|
||||
android:name="fr.antelop.issuer_id"
|
||||
android:value="treezor" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
@@ -34,6 +35,25 @@
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- Wrap FCM with Antelop SDK forwarding (see AntelopAwareMessagingService). -->
|
||||
<service
|
||||
android:name=".AntelopAwareMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- Disable Antelop's stock FCM service so AntelopAwareMessagingService is the only handler. -->
|
||||
<service
|
||||
android:name="fr.antelop.exposed.DefaultAntelopFirebaseMessagingService"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- Disable the firebase_messaging plugin's FCM service so AntelopAwareMessagingService is the only handler. -->
|
||||
<service
|
||||
android:name="io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingService"
|
||||
tools:node="remove" />
|
||||
</application>
|
||||
|
||||
<!-- Required to query activities that can process text, see:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.savefamily.app
|
||||
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import fr.antelop.sdk.firebase.AntelopFirebaseMessagingUtil
|
||||
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingService
|
||||
|
||||
/**
|
||||
* FCM service that gives the Antelop SDK first dibs on every push, then
|
||||
* delegates the rest to the firebase_messaging Flutter plugin so Dart still
|
||||
* receives the notifications it expects.
|
||||
*
|
||||
* Without this, only one FirebaseMessagingService can win the
|
||||
* com.google.firebase.MESSAGING_EVENT intent — and once we added the
|
||||
* firebase_messaging plugin, its FlutterFirebaseMessagingService started
|
||||
* winning over Antelop's DefaultAntelopFirebaseMessagingService, leaving the
|
||||
* SDK forever waiting for activation pushes that never reached it.
|
||||
*/
|
||||
class AntelopAwareMessagingService : FlutterFirebaseMessagingService() {
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
val handled = AntelopFirebaseMessagingUtil.onMessageReceived(
|
||||
applicationContext,
|
||||
remoteMessage,
|
||||
)
|
||||
if (!handled) {
|
||||
super.onMessageReceived(remoteMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
AntelopFirebaseMessagingUtil.onTokenRefresh(applicationContext)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="fr.antelop.application_id"
|
||||
android:value="8632355012486459749" />
|
||||
android:value="4713640103500149457" />
|
||||
<meta-data
|
||||
android:name="fr.antelop.issuer_id"
|
||||
android:value="treezor" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "950566980029",
|
||||
"project_id": "sf-platform-pro",
|
||||
"storage_bucket": "sf-platform-pro.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:950566980029:android:75a7c10b6259d09681aad4",
|
||||
"android_client_info": {
|
||||
"package_name": "com.savefamily.app"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDkjNdOAK0ype7wgdgiC1BCKV_pP4s_mlA"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="fr.antelop.application_id"
|
||||
android:value="3381448747424346509" />
|
||||
android:value="4713640103500149457" />
|
||||
<meta-data
|
||||
android:name="fr.antelop.issuer_id"
|
||||
android:value="treezor" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"env": "development",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es"
|
||||
"apiOrigin": "https://neki-b2b.neki.es",
|
||||
"wsUrl": "wss://api-neki-b2b.neki.es/websocket"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"env": "production",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es"
|
||||
}
|
||||
"apiBaseUrl": "https://api-platform.savefamily.app/gateway/api/",
|
||||
"apiOrigin": "https://platform.savefamily.app",
|
||||
"wsUrl": "wss://api-platform.savefamily.app/websocket"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"env": "staging",
|
||||
"apiBaseUrl": "https://api-platform.pre.savefamilygps.net/gateway/api/",
|
||||
"apiOrigin": "https://platform.pre.savefamilygps.net"
|
||||
"apiOrigin": "https://platform.pre.savefamilygps.net",
|
||||
"wsUrl": "wss://api-platform.pre.savefamilygps.net/websocket"
|
||||
}
|
||||
637
apps/mobile_app/docs/analytics-catalog-technical.md
Normal file
637
apps/mobile_app/docs/analytics-catalog-technical.md
Normal file
@@ -0,0 +1,637 @@
|
||||
# Catálogo de Analíticas — SaveFamily (módulo legacy)
|
||||
|
||||
> Documento para el equipo de Marketing. Describe cada evento de Firebase
|
||||
> Analytics que la app envía desde el módulo legacy: qué significa, cuándo se
|
||||
> dispara, qué parámetros trae, y qué insight ofrece.
|
||||
>
|
||||
> **Ambiente:** Eventos visibles en vivo en Firebase Console Analytics
|
||||
> **DebugView** (para builds debug/profile con el flag de debug activado).
|
||||
> Los reportes históricos están en **Realtime**, **Engagement Events** y
|
||||
> **Engagement Pages and screens**.
|
||||
>
|
||||
> **Parámetro común:** Cada evento incluye automáticamente un parámetro
|
||||
> `consent_status` (`true` / `false`) para permitir filtrado por
|
||||
> consentimiento GDPR cuando corresponda.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [User Properties (propiedades del usuario)](#user-properties)
|
||||
2. [Screen Views (vistas de pantalla automáticas)](#screen-views)
|
||||
3. [Autenticación (`legacy_auth_*`)](#autenticación)
|
||||
4. [Cuenta (`legacy_account_*`)](#cuenta)
|
||||
5. [Dispositivo — Setup / alta (`legacy_device_setup_*`)](#dispositivo--setup)
|
||||
6. [Dispositivo — Funciones (`legacy_device_*`)](#dispositivo--funciones)
|
||||
7. [Contactos del dispositivo (`legacy_contacts_*`)](#contactos-del-dispositivo)
|
||||
8. [Ajustes (`legacy_settings_*`)](#ajustes)
|
||||
9. [Soporte (`legacy_support_*`)](#soporte)
|
||||
10. [Onboarding (`legacy_onboarding_*`)](#onboarding)
|
||||
11. [Panel principal (`legacy_control_panel_*`)](#panel-principal)
|
||||
12. [Ubicación y mapa (`legacy_location_*`)](#ubicación-y-mapa)
|
||||
|
||||
---
|
||||
|
||||
## User Properties
|
||||
|
||||
Son propiedades que se setean una sola vez por usuario (al hacer login) y
|
||||
sirven para **segmentar** a los usuarios en los reportes. Cualquier evento
|
||||
puede cruzarse por estas dimensiones en Firebase Analytics.
|
||||
|
||||
| Propiedad | Descripción | Valores ejemplo | Cuándo se setea |
|
||||
|---|---|---|---|
|
||||
| `env` | Ambiente de la app | `development`, `staging`, `production` | Al arrancar la app |
|
||||
| `user_id` (interna) | Identificador único del usuario | UUID del backend | Al confirmar el login (después del 2FA) |
|
||||
| `user_role` | Rol del usuario en el backend | `client`, `admin`, etc. | Al login |
|
||||
| `user_language` | Idioma preferido del usuario | `es`, `en`, `fr`, `de`, `it`, `pt` | Al login |
|
||||
| `user_signup_date` | Fecha de creación de la cuenta (ISO 8601 UTC) | `2024-04-07T10:34:42.000Z` | Al login |
|
||||
| `user_has_phone` | Si tiene teléfono registrado | `true` / `false` | Al login |
|
||||
| `user_has_api_key` | Si tiene una API key asignada (usuario técnico) | `true` / `false` | Al login |
|
||||
|
||||
> **Nota futura:** Cuando se lance el plan premium, se agregará
|
||||
> `user_plan` (`free` / `premium` / `family`) para segmentar la base por
|
||||
> plan.
|
||||
|
||||
---
|
||||
|
||||
## Screen Views
|
||||
|
||||
Cada vez que el usuario navega a una pantalla, Firebase recibe un evento
|
||||
automático `screen_view` con el parámetro `screen_name` igual al nombre
|
||||
lógico de la ruta (no el nombre de clase Flutter).
|
||||
|
||||
**Esto se captura automáticamente**, sin instrumentación manual en cada
|
||||
pantalla, mediante un listener del router. **También captura los cambios de
|
||||
tab del bottom navigation** (home device functions mapa chat).
|
||||
|
||||
### Pantallas del módulo legacy que se trackean
|
||||
|
||||
| Screen name | Pantalla |
|
||||
|---|---|
|
||||
| `splash` | Pantalla de carga inicial |
|
||||
| `legacy_onboarding` | Intro/onboarding |
|
||||
| `legacy_login` | Pantalla de login |
|
||||
| `legacy_signup` | Alta de cuenta |
|
||||
| `legacy_recover_password` | Recuperación de contraseña |
|
||||
| `legacy_device_setup` | Wizard de alta de reloj/dispositivo |
|
||||
| `legacy_request_link_phone` | Inicio de vinculación de teléfono |
|
||||
| `legacy_verify_link_phone_code` | Verificación del código OTP |
|
||||
| `control_panel` | Dashboard principal (home del legacy) |
|
||||
| `customer_service` | Pantalla de soporte |
|
||||
| `account_settings` | Menú de cuenta |
|
||||
| `personal_data` | Editar datos personales |
|
||||
| `change_password` | Cambiar contraseña |
|
||||
| `linked_devices` | Dispositivos vinculados a la cuenta |
|
||||
| `app_users` | Sub-usuarios de la app |
|
||||
| `delete_account` | Flujo de eliminación de cuenta |
|
||||
| `device_management` | Menú de gestión del dispositivo |
|
||||
| `scheduled_activities` | Actividades programadas |
|
||||
| `contacts` | Contactos del dispositivo |
|
||||
| `edit_contact` | Editar un contacto |
|
||||
| `health` | Salud (ritmo cardíaco, SpO2) |
|
||||
| `remote_connection` | Conexión remota (cámara, llamada) |
|
||||
| `locate_device` | Localizar dispositivo |
|
||||
| `rewards` | Recompensas |
|
||||
| `activity_meter` | Medidor de actividad (pasos) |
|
||||
| `apps_use` | Uso de apps |
|
||||
| `volume_control` | Control de volumen |
|
||||
| `call_history` | Historial de llamadas |
|
||||
| `background_image` | Imagen de fondo del dispositivo |
|
||||
| `legacy_location` | Mapa de ubicación |
|
||||
| `legacy_chat` | Chat (placeholder) |
|
||||
| `settings` | Menú de ajustes |
|
||||
| `alarm` | Alarmas |
|
||||
| `remote_management` | Gestión remota |
|
||||
| `sos_agenda` | Contactos SOS |
|
||||
| `sound` | Sonido del dispositivo |
|
||||
| `sync_clock` | Sincronización de reloj |
|
||||
| `app_store` | Gestión de apps instaladas |
|
||||
| `battery` | Batería / modo nocturno |
|
||||
| `block_phone` | Bloqueo de teléfono (whitelist) |
|
||||
| `disable_functions` | Desactivar funciones (teclado, GPS) |
|
||||
| `language` | Idioma del dispositivo |
|
||||
| `legacy_notifications` | Notificaciones del dispositivo |
|
||||
| `remote_on_off` | Encendido/apagado remoto |
|
||||
| `alerts` | Alertas |
|
||||
| `timezone` | Zona horaria |
|
||||
| `wifi_settings` | Configuración WiFi |
|
||||
|
||||
**Insight para marketing:** Con estas screen_view podés construir funnels
|
||||
(ej: `legacy_login control_panel device_management locate_device`) y
|
||||
medir tiempos entre pantallas, rebotes y pantallas más visitadas.
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
Prefijo `legacy_auth_*` — cubre login, 2FA, signup, recuperación de
|
||||
contraseña, vinculación de teléfono y logout.
|
||||
|
||||
### Login
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_login_attempt` | El usuario pulsa "Iniciar sesión" después de validar el formulario en el cliente. | — | Tope del funnel de login. Usar como base del "100 %" del funnel. |
|
||||
| `legacy_auth_login_success` | El backend aceptó email + contraseña. Aún falta el 2FA. | — | Credenciales válidas. Usar para medir la calidad de la contraseña/email. |
|
||||
| `legacy_auth_login_failure` | El backend rechazó las credenciales o hubo un error de red. | `reason` (mensaje de error) | Fricción. Analizar los `reason` más frecuentes para detectar problemas. |
|
||||
|
||||
### 2FA (doble factor)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_2fa_requested` | El backend envió el código 2FA al usuario. | — | Usuario pasó el primer paso del login. |
|
||||
| `legacy_auth_2fa_verified` | El código 2FA fue verificado y la sesión está activa. | — | Login exitoso. Fin del funnel de login. |
|
||||
| `legacy_auth_2fa_failure` | El código 2FA fue rechazado (incorrecto, expirado). | `reason` | Fricción en el 2FA. Si es muy alto, puede indicar problemas con la entrega del código. |
|
||||
| `legacy_auth_2fa_resend` | El usuario pidió reenviar el código. | — | Indica que no le llegó el primero. Útil para medir problemas de entrega. |
|
||||
|
||||
### Signup
|
||||
|
||||
El signup es un wizard de **2 pasos** (`step_index` 0, 1):
|
||||
|
||||
- **Paso 0 — Datos personales:** nombre, apellido, email, teléfono (con picker de país), aceptación de términos.
|
||||
- **Paso 1 — Contraseña:** password y repeat password con validación de reglas.
|
||||
|
||||
El `language` se infiere del locale del dispositivo al momento del submit.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_signup_started` | El usuario envió el formulario final (submit del paso 1). | — | Top del funnel de request al backend. |
|
||||
| `legacy_auth_signup_completed` | El backend creó la cuenta exitosamente. | — | Conversión de nueva cuenta. |
|
||||
| `legacy_auth_signup_failed` | Error en el alta (email ya existe, datos inválidos, error de red). | `reason` | Drop-off del signup. Analizar `reason` para ver si hay patrones. |
|
||||
| `legacy_auth_signup_step_completed` | El usuario avanzó a un paso siguiente (validación pasó). | `step_index` (0, 1) — el paso que JUSTO terminó | Funnel interno del signup. Permite ver cuántos completan paso 0 y llegan al 1. |
|
||||
| `legacy_auth_signup_step_back` | El usuario tocó "atrás" dentro del wizard. | `step_index` — el paso del que vuelve | Indica que el usuario quiere corregir algo — fricción. |
|
||||
| `legacy_auth_signup_step_validation_failed` | El usuario tocó "siguiente" pero la validación del formulario lo rechazó. | `step_index` | **Muy valioso:** dice en qué paso hay más problemas de validación. Si step 0 falla: nombre/apellido, email, teléfono o términos. Si step 1 falla: reglas de contraseña. |
|
||||
|
||||
### Recuperación de contraseña
|
||||
|
||||
Flujo **exclusivo por email** (se removió la opción de SMS/teléfono).
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_password_reset_requested` | El usuario inició el flujo de recuperar contraseña tipeando su email. | — | Fricción: alguien no recuerda su contraseña. |
|
||||
| `legacy_auth_password_reset_email_sent` | El backend confirmó el envío del email de recuperación. | — | Confirma que el email salió. Cruzar con `reset_requested` para medir fallas. |
|
||||
| `legacy_auth_password_reset_completed` | El usuario guardó la nueva contraseña exitosamente. | — | **Conversión final** del funnel de recuperación. |
|
||||
| `legacy_auth_password_reset_failed` | Error al intentar guardar la nueva contraseña. | `reason` (`unequal_passwords`, `too_short`, `no_capitals`, `no_numbers`, `no_special_chars`, o mensaje del backend) | Permite ver qué reglas de validación molestan más a los usuarios. |
|
||||
|
||||
### Vinculación de teléfono
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_link_phone_code_requested` | El usuario envió su número y pidió el código OTP. | — | Inicio del flujo de linking. |
|
||||
| `legacy_auth_link_phone_code_request_failed` | Falló el pedido del código al backend. | `reason` | Fricción inicial. |
|
||||
| `legacy_auth_link_phone_code_verified` | El código OTP fue verificado con éxito. | — | Número vinculado. |
|
||||
| `legacy_auth_link_phone_code_verification_failed` | Falló la verificación (código incorrecto o expirado). | `reason` | Fricción en el segundo paso. |
|
||||
|
||||
### Logout
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_logout` | El usuario cerró sesión y la app limpió la sesión local. | — | Señal de fin de sesión. Cruzar con duración de sesión para ver patrones de uso. |
|
||||
|
||||
---
|
||||
|
||||
## Cuenta
|
||||
|
||||
Prefijo `legacy_account_*` — cubre edición de perfil, contraseña,
|
||||
dispositivos vinculados, usuarios de la app y **eliminación de cuenta
|
||||
(señal crítica de churn)**.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_account_personal_data_edited` | El usuario guardó cambios en sus datos personales (nombre, apellido, teléfono). | — | Engagement con la cuenta. |
|
||||
| `legacy_account_password_changed` | Cambio de contraseña exitoso. | — | Señal de buen hábito de seguridad. |
|
||||
| `legacy_account_password_change_failed` | El cambio de contraseña falló. | `reason` | Fricción. |
|
||||
| `legacy_account_linked_device_unlinked` | El usuario quitó un dispositivo vinculado de su cuenta. | — | Posible señal temprana de desuso del dispositivo. |
|
||||
| `legacy_account_linked_device_renamed` | El usuario renombró un dispositivo vinculado (editó el carrier name). | — | Personalización / engagement con la gestión de dispositivos. |
|
||||
| `legacy_account_app_user_delete_triggered` | El usuario tocó "eliminar" en la pantalla de app users. | — | Nota técnica: la implementación actual borra al usuario logueado (parece ser placeholder). El evento se mantiene para medir demanda del feature. |
|
||||
| `legacy_account_deletion_initiated` | **CHURN SIGNAL** — El usuario entró al flujo "Eliminar cuenta". | — | Top del funnel de churn. |
|
||||
| `legacy_account_deletion_confirmed` | El usuario confirmó la eliminación y la API call está en progreso. | — | El usuario quiere realmente irse. |
|
||||
| `legacy_account_deletion_completed` | El backend confirmó la eliminación. | — | Usuario perdido. |
|
||||
| `legacy_account_deletion_cancelled` | El usuario canceló antes de confirmar la eliminación. | — | Save: el usuario se arrepintió. Útil para medir efectividad de pantallas de retención. |
|
||||
|
||||
---
|
||||
|
||||
## Dispositivo — Setup
|
||||
|
||||
Prefijo `legacy_device_setup_*` — **el momento aha del producto**: vincular
|
||||
un reloj/dispositivo del hijo a la cuenta del padre/madre.
|
||||
|
||||
### Funnel del wizard
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_started` | El usuario entró al wizard de alta de dispositivo. | — | Top del funnel de activación. |
|
||||
| `legacy_device_setup_step_completed` | El usuario completó un paso del wizard. | `step` (`intro`, `link_info`, `scan_watch`, `profile`), `duration_seconds` (cuánto tardó en ese paso) | Permite ver dónde se abandona más el wizard **Y cuánto tiempo pasan los usuarios en cada paso** — fricción directa. |
|
||||
| `legacy_device_setup_completed` | El dispositivo se creó exitosamente y está vinculado. | `child_gender` (M/F/other), `relation_type` (mother/father/etc), `child_age_years` | **Conversión de activación + demográficos del usuario final**. Marketing puede construir **personas reales** con estos 3 params: género, edad y relación con el adulto que compró. |
|
||||
| `legacy_device_setup_failed` | Falló un paso del wizard. | `at_step` (en qué paso falló), `reason` (error) | Señal para el equipo técnico de dónde hay problemas. |
|
||||
| `legacy_device_setup_cancelled` | El usuario volvió atrás y abandonó el wizard. | `at_step` | Drop-off del wizard. |
|
||||
|
||||
### Entrada del código del reloj (QR vs. manual)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_qr_scanned` | El usuario escaneó exitosamente el código QR del reloj. | — | Método "rápido". Si su ratio baja, el QR scanner puede estar fallando. |
|
||||
| `legacy_device_setup_manual_code_entered` | El usuario avanzó con el código tipeado manualmente (no escaneó). | — | Fallback. Si crece mucho el ratio vs QR, invertir en mejorar la UX del scanner. |
|
||||
|
||||
### Familias con múltiples hijos
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_reset_for_new_kid` | Después de terminar un alta, el usuario tocó "agregar otro hijo". | — | **Señal de familia con múltiples hijos**. Estos usuarios típicamente tienen mayor retention y LTV — son el mejor segmento. |
|
||||
|
||||
---
|
||||
|
||||
## Dispositivo — Funciones
|
||||
|
||||
Prefijo `legacy_device_*` — acciones sobre el dispositivo ya vinculado.
|
||||
Mide qué features del producto se usan más.
|
||||
|
||||
### Localización del dispositivo (comando "find")
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_locate_requested` | El usuario pulsó el botón de localizar (intento). | — | Uso del feature principal del producto. Top del mini-funnel de localización. |
|
||||
| `legacy_device_locate_success` | El comando de localizar fue enviado con éxito al backend. | — | El dispositivo va a sonar. Conversión del mini-funnel. |
|
||||
| `legacy_device_locate_failure` | El comando de localizar falló (error del backend o de red). | `reason` | Problema técnico al localizar. Drop-off del mini-funnel. |
|
||||
|
||||
### Conexión remota (cámara + llamadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_remote_connection_started` | El usuario entró a la pantalla de conexión remota. | — | Intención de interactuar remotamente. |
|
||||
| `legacy_device_remote_connection_photo_taken` | El usuario pidió una foto de la cámara remota. | — | Feature avanzada. Permite medir uso de la cámara del reloj. |
|
||||
| `legacy_device_remote_connection_call_initiated` | El usuario inició una llamada bidireccional. | — | Feature crítica: llamar al niño. |
|
||||
| `legacy_device_remote_connection_picture_viewed` | El usuario navegó entre fotos de la cámara remota. | `direction` (`next`, `prev`, `direct`) | Engagement con la galería: cuántas fotos revisa el padre después de pedirlas. |
|
||||
|
||||
### Volumen del dispositivo
|
||||
|
||||
Cada envío del formulario dispara **un evento por tipo de volumen que
|
||||
efectivamente cambió** (media, ringtone, alarm) — si el usuario movió solo
|
||||
el media, solo se manda ese. Permite medir qué tipo de sonido configuran
|
||||
más los padres.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_volume_control_changed` | El usuario guardó un cambio de volumen en el dispositivo, se emite 1 vez por cada tipo modificado. | `type` (`media`, `ringtone`, `alarm`), `level` (0-100) | Configuración. Cruzar `type` para ver cuál se ajusta más. |
|
||||
|
||||
### Imagen de fondo del reloj
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_background_image_changed` | El usuario seleccionó una imagen existente como fondo. | — | Personalización. |
|
||||
| `legacy_device_background_image_uploaded` | El usuario subió una foto personal como fondo. | — | Alta personalización — indicador de engagement. |
|
||||
|
||||
### Actividades programadas (alarmas personalizadas del dispositivo)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_scheduled_activity_added` | El usuario agregó una actividad programada. | `week_day` (0-6, 0 = domingo), `period` (`HH:mm-HH:mm`) | **Dato muy útil:** permite ver qué horarios programan los padres (desayuno, colegio, deberes, etc) y qué días. |
|
||||
| `legacy_device_scheduled_activity_updated` | El usuario editó una actividad programada. | `week_day`, `period` | Refinamiento de configuración. |
|
||||
| `legacy_device_scheduled_activity_removed` | El usuario eliminó una actividad programada. | — | |
|
||||
|
||||
### Recompensas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_rewards_granted` | El usuario asignó minutos de recompensa al dispositivo. | `amount` (cantidad de minutos) | Gamificación / recompensas de uso. |
|
||||
|
||||
### Podómetro (Activity Meter)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_activity_pedometer_toggled` | El usuario activó/desactivó el contador de pasos. | `enabled` (`true` / `false`) | |
|
||||
| `legacy_device_activity_meter_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de pasos. | `range` (`today`, `seven_days`, `thirty_days`, `custom`) | **Engagement profundo:** el padre no solo abre la pantalla, sino que investiga distintos períodos. |
|
||||
|
||||
### Salud (ritmo cardíaco / SpO2)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_health_heart_rate_frequency_changed` | El usuario cambió la frecuencia de medición del ritmo cardíaco. | `frequency_seconds` | Personalización de monitoreo de salud. |
|
||||
| `legacy_device_health_measurement_started` | El usuario inició una medición manual de ritmo cardíaco. | — | Interés en datos de salud del niño. |
|
||||
| `legacy_device_health_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de salud. | `range` | Engagement profundo: padres revisando el historial de salud. |
|
||||
|
||||
### Uso de aplicaciones (Apps Use)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_apps_use_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de uso de apps. | `range`, `total_duration_seconds` (total acumulado del período), `top_app_name` (app más usada en ese período) | **El evento más rico del módulo.** Permite a marketing segmentar directo: "padres cuyos hijos usan más TikTok que YouTube", "familias con uso > 4hs/día", etc. |
|
||||
|
||||
### Historial de llamadas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_call_history_filter_changed` | El usuario cambió el filtro del historial. | `filter` (`all`, `incoming`, `outgoing`, `missed`) | **Cuando se filtra `missed` es señal de preocupación** del padre: busca llamadas perdidas del hijo. |
|
||||
|
||||
---
|
||||
|
||||
## Contactos del dispositivo
|
||||
|
||||
Prefijo `legacy_contacts_*` — contactos permitidos para llamadas desde el
|
||||
dispositivo del niño.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_contacts_added` | El usuario agregó un contacto al dispositivo. | `total_count` (cantidad total de contactos DESPUÉS del add) | Configuración inicial o expansión de la agenda. El `total_count` permite segmentar "padres con agenda chica vs grande". |
|
||||
| `legacy_contacts_edited` | El usuario editó un contacto existente. | — | |
|
||||
| `legacy_contacts_deleted` | El usuario eliminó un contacto del dispositivo. | `total_count` (cantidad total DESPUÉS del delete) | |
|
||||
|
||||
---
|
||||
|
||||
## Ajustes
|
||||
|
||||
Prefijo `legacy_settings_*` — configuración general del dispositivo
|
||||
(alarmas, SOS, bloqueos, idioma, red, etc).
|
||||
|
||||
### Alarmas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_alarm_added` | Alarma nueva creada. | `time` (`HH:mm`) | Uso del feature de alarma. El `time` permite ver qué horarios son más populares (despertador matutino, hora del colegio, etc). |
|
||||
| `legacy_settings_alarm_updated` | Alarma existente editada. | `time` (el NUEVO `HH:mm`) | Refinamiento. |
|
||||
| `legacy_settings_alarm_removed` | Alarma eliminada. | — | |
|
||||
|
||||
### Contactos SOS
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_sos_contact_added` | Contacto SOS agregado. | `total_count` | Configuración de seguridad. Muy importante. |
|
||||
| `legacy_settings_sos_contact_removed` | Contacto SOS removido. | `total_count` | |
|
||||
|
||||
### Whitelist del teléfono (bloqueo de llamadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_block_phone_contact_added` | Contacto agregado a la whitelist de llamadas permitidas. | `total_count` | Control parental. |
|
||||
| `legacy_settings_block_phone_contact_removed` | Contacto removido de la whitelist. | `total_count` | |
|
||||
|
||||
### Control parental (funciones desactivadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_disable_functions_changed` | El usuario guardó cambios en la pantalla de funciones desactivadas. | — | Engagement con control parental (evento agregado). |
|
||||
| `legacy_settings_disable_functions_keyboard_toggled` | Se guardó con el teclado habilitado/deshabilitado. | `enabled` | Control granular. |
|
||||
| `legacy_settings_disable_functions_gps_toggled` | Se guardó con el GPS habilitado/deshabilitado. | `enabled` | Control granular. |
|
||||
|
||||
### Otros ajustes del dispositivo
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_language_changed` | Se cambió el idioma del dispositivo. | `language` (ej. `es`, `en`) | |
|
||||
| `legacy_settings_alerts_configured` | El usuario guardó cambios en las alertas. | `alert_count` (cuántas alertas activas), `alerts_enabled` (lista separada por comas truncada a 100 chars) | Permite ver qué alertas son más populares y cuántas alertas promedio configuran los padres. |
|
||||
| `legacy_settings_timezone_changed` | Se cambió la zona horaria. | `timezone` | |
|
||||
| `legacy_settings_wifi_added` | Se agregó una red WiFi permitida. | `total_count` | |
|
||||
| `legacy_settings_wifi_removed` | Se eliminó una red WiFi permitida. | `total_count` | |
|
||||
| `legacy_settings_sound_changed` | Se cambió el modo de sonido del dispositivo. | `mode` (`normal` / `silent` / `vibrate`) | Preferencia de perfil sonoro del niño. |
|
||||
| `legacy_settings_sync_clock_triggered` | El usuario disparó una sincronización manual del reloj del dispositivo. | — | |
|
||||
| `legacy_settings_battery_night_mode_toggled` | El usuario activó/desactivó el modo nocturno (ahorro de batería). | `enabled` | |
|
||||
|
||||
### Gestión remota del dispositivo (comandos destructivos)
|
||||
|
||||
Estos eventos son **muy importantes** para churn analysis. Un
|
||||
`factory_reset` típicamente precede a un desvinculado y potencial churn.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_remote_management_shutdown` | El usuario apagó el dispositivo remotamente. | — | Acción poco frecuente. |
|
||||
| `legacy_settings_remote_management_restart` | El usuario reinició el dispositivo remotamente. | — | Típicamente usado cuando hay problemas técnicos. |
|
||||
| `legacy_settings_remote_management_factory_reset` | **CHURN SIGNAL** — El usuario reseteó el dispositivo a fábrica. | — | Borra el dispositivo. Frecuentemente precede un `legacy_account_linked_device_unlinked` y luego `legacy_account_deletion_*`. Cruzar para medir correlación. |
|
||||
|
||||
---
|
||||
|
||||
## Soporte
|
||||
|
||||
Prefijo `legacy_support_*` — solo 1 evento hoy, medirá la demanda de
|
||||
soporte.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_support_contact_initiated` | El usuario tocó el botón para contactar soporte (ej. abrir el cliente de email). | `channel` (`email` hoy; en el futuro también `phone`, `whatsapp`), `country` (país seleccionado en el formulario) | Demanda de soporte **por país**: permite ver dónde se originan más tickets. Nota: mide la **intención** de contactar, no confirma envío. |
|
||||
|
||||
---
|
||||
|
||||
## Onboarding
|
||||
|
||||
Prefijo `legacy_onboarding_*` — los slides de intro iniciales de la app.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_onboarding_step_changed` | El usuario pasó a un nuevo slide del intro. | `step_index` (número de slide, empieza en 0) | Medir cuántos slides el usuario ve antes de empezar. |
|
||||
|
||||
---
|
||||
|
||||
## Panel principal
|
||||
|
||||
Prefijo `legacy_control_panel_*` — acciones en el home del legacy.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_control_panel_device_selected` | El usuario cambió el dispositivo activo (útil cuando hay varios hijos). | `total_devices` (cuántos dispositivos tiene vinculados) | Qué dispositivo está monitoreando activamente. El `total_devices` permite **segmentar por tamaño de familia** (1 hijo, 2 hijos, 3+). |
|
||||
| `legacy_control_panel_positions_refreshed` | El usuario tiró del pull-to-refresh o tocó "actualizar" en el dashboard. | — | Preocupación activa del usuario. Indicador de engagement alto. |
|
||||
|
||||
---
|
||||
|
||||
## Ubicación y mapa
|
||||
|
||||
Prefijo `legacy_location_*` — **el feature más rico del producto**. Acá
|
||||
capturamos toda la interacción del usuario con el mapa: ver el trayecto,
|
||||
crear zonas seguras, ver lugares frecuentes, cambiar frecuencia de
|
||||
actualización, etc.
|
||||
|
||||
### Geofences (zonas seguras) — CRUD básico
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_geofence_created` | Se creó una geofence (API confirmó). | — | **Conversión final** del funnel de creación. |
|
||||
| `legacy_location_geofence_updated` | Se actualizó una geofence existente. | — | Refinamiento de configuración de zonas. |
|
||||
| `legacy_location_geofence_deleted` | Se eliminó una geofence. | — | |
|
||||
|
||||
### Lugares frecuentes — CRUD básico
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_frequent_place_created` | Se creó un lugar frecuente (API confirmó). | — | **Conversión final** del funnel. |
|
||||
| `legacy_location_frequent_place_updated` | Se actualizó un lugar frecuente. | — | |
|
||||
| `legacy_location_frequent_place_deleted` | Se eliminó un lugar frecuente. | — | |
|
||||
|
||||
### Funnel de creación de lugares (geofences y frequent places)
|
||||
|
||||
Este es el funnel más valioso del módulo de ubicación. Permite medir
|
||||
**cuánta gente empieza a crear una zona vs. cuánta termina**.
|
||||
|
||||
```
|
||||
legacy_location_place_creation_started (top: 100 %)
|
||||
|
||||
legacy_location_point_confirmed (paso 1 completado)
|
||||
|
||||
legacy_location_radius_confirmed (solo geofences — paso 2)
|
||||
|
||||
legacy_location_geofence_created (bottom: API OK)
|
||||
o legacy_location_frequent_place_created
|
||||
```
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_place_creation_started` | El usuario tocó "agregar zona" o "agregar lugar frecuente". | `mode` (`geofence` / `frequent_place`) | Top del funnel. |
|
||||
| `legacy_location_point_confirmed` | El usuario tocó el mapa para fijar el centro del lugar. | `mode` | Paso 1 del funnel completado. |
|
||||
| `legacy_location_radius_confirmed` | El usuario confirmó el radio de la geofence (solo aplica a geofences). | `radius` (metros), `is_editing` (`true` si estaba editando una existente, `false` si es nueva) | Paso 2 del funnel completado. Permite también **analizar qué tamaños de zonas eligen los usuarios** (radios más comunes casa, escuela, etc.). |
|
||||
| `legacy_location_place_creation_cancelled` | El usuario salió del flujo de creación/edición antes de terminar. | `mode`, `at_step` (`picking_point` o `adjusting_radius`) | **Drop-off del funnel**. El `at_step` dice exactamente dónde lo perdimos. |
|
||||
|
||||
### Exploración y edición
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_geofence_selected` | El usuario tocó una geofence del mapa para verla. | — | Engagement: el usuario está mirando sus zonas. |
|
||||
| `legacy_location_geofence_dismissed` | El usuario cerró el popup de la geofence sin hacer nada. | — | "Miró pero no editó". Indicador de exploración. |
|
||||
| `legacy_location_geofence_edit_started` | El usuario tocó "editar" en una geofence seleccionada. | — | Intención de editar. Mid-funnel de edición. |
|
||||
| `legacy_location_frequent_place_selected` | El usuario tocó un lugar frecuente para verlo. | — | Engagement. |
|
||||
| `legacy_location_frequent_place_dismissed` | El usuario cerró el popup del lugar frecuente. | — | |
|
||||
| `legacy_location_history_position_selected` | El usuario tocó un punto del historial de ubicaciones en el mapa. | — | Inspección detallada del trayecto. |
|
||||
| `legacy_location_history_position_dismissed` | El usuario cerró el detalle del punto de historial. | — | |
|
||||
|
||||
### Historial de ubicaciones
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_history_loaded` | El usuario cargó el historial para un rango de fechas. | — | Interés en el historial. |
|
||||
| `legacy_location_history_cleared` | El usuario limpió el trayecto del mapa. | — | |
|
||||
|
||||
### Frecuencia de ubicación (privacidad vs. precisión)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_frequency_updated` | El usuario cambió cada cuánto el dispositivo manda su posición. | `frequency_seconds` (ej. `60`, `300`, `900`) | **Dato súper útil:** indica preferencia entre privacidad y precisión/batería. Correlacionar con retention. |
|
||||
|
||||
### Capas del mapa (toggles de visibilidad)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_map_geofences_toggled` | El usuario mostró/ocultó las geofences en el mapa. | `visible` (`true` / `false`) | |
|
||||
| `legacy_location_map_frequent_places_toggled` | El usuario mostró/ocultó los lugares frecuentes. | `visible` | |
|
||||
| `legacy_location_map_route_trail_toggled` | El usuario mostró/ocultó la línea del trayecto histórico. | `visible` | |
|
||||
|
||||
### Modo "seguir en vivo"
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_following_toggled` | El usuario activó/desactivó el modo "seguir dispositivo" (el mapa se re-centra automáticamente). | `enabled` (`true` / `false`) | **Engagement alto:** el usuario está viendo al hijo en tiempo real. Correlacionar con horarios (ej. entrada/salida del cole). |
|
||||
|
||||
### UI del mapa (chrome)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_map_actions_expanded` | El usuario abrió/cerró el drawer de acciones del mapa. | `expanded` (`true` / `false`) | Indica conocimiento de la UI. |
|
||||
| `legacy_location_map_zoomed` | El usuario hizo zoom in/out y se quedó en ese nivel (con debounce de 1 segundo para no spamear). | `zoom` (nivel de zoom final) | Nivel de detalle con el que los usuarios miran el mapa. Un zoom alto indica "me importa ver dónde exactamente está". |
|
||||
| `legacy_location_map_style_changed` | El usuario eligió otro estilo visual para el mapa desde el selector de capas. | `style` (`standard` / `voyager` / `light` / `dark` / `satellite`) | Personalización de la experiencia. **Satellite** es el más usado por padres que quieren ver edificios reales. |
|
||||
| `legacy_location_map_center_tapped` | El usuario tocó el botón "centrar en el dispositivo" del mapa. | — | Acción de re-centrado manual. Indica que el mapa se desplazó y el usuario quiere volver al hijo. |
|
||||
| `legacy_location_map_refresh_tapped` | El usuario tocó el botón de refresco dentro del mapa (distinto del pull-to-refresh del control panel). | — | **Engagement intenso:** el usuario quiere la posición más reciente AHORA. Suele dispararse en momentos de ansiedad. |
|
||||
| `legacy_location_shared` | El usuario tocó "compartir ubicación" — abre el share sheet nativo para mandar la posición del hijo a otra app. | — | **Acción viral del producto.** Es la más importante para crecimiento orgánico: indica que el usuario está mandando data del producto a contactos fuera de la app (familia, pareja, abuelos). |
|
||||
| `legacy_location_list_sheet_opened` | El usuario abrió el bottom sheet con la lista de geofences, lugares frecuentes e historial. | — | Quiere explorar todo lo que tiene configurado. Mid-funnel de gestión. |
|
||||
| `legacy_location_history_type_filter_changed` | El usuario filtró el historial por tipo de posición. | `type` (`gps` / `wifi` / `sos` / `all` cuando limpia el filtro) | Indica interés en una fuente de datos específica. **`sos`** filtrado es señal de un evento crítico que el usuario está investigando. |
|
||||
|
||||
---
|
||||
|
||||
## Cómo usar este catálogo
|
||||
|
||||
### Para construir funnels
|
||||
Tomá un evento "inicio" y uno "fin" en Firebase Analytics Engagement
|
||||
**Funnels** y comparalos:
|
||||
- **Signup:** `legacy_auth_signup_started _completed`
|
||||
- **Login:** `legacy_auth_login_attempt _2fa_verified`
|
||||
- **Activación (aha moment):** `legacy_device_setup_started _completed`
|
||||
- **Creación de zona segura:** `legacy_location_place_creation_started _geofence_created`
|
||||
- **Churn:** `legacy_account_deletion_initiated _deletion_completed`
|
||||
|
||||
### Para segmentar audiencias
|
||||
En **Audiences** podés filtrar por user properties (`user_language`,
|
||||
`user_has_phone`, etc.) y cruzarlo con cualquiera de estos eventos.
|
||||
|
||||
### Para detectar problemas
|
||||
Filtrar por los eventos con `_failed` o `_failure` y mirar los `reason`
|
||||
más frecuentes en la pestaña Events Parameter.
|
||||
|
||||
### Para medir engagement diario
|
||||
Los eventos `legacy_control_panel_positions_refreshed`,
|
||||
`legacy_location_following_toggled` y las screen_views del mapa son los
|
||||
indicadores más fuertes de usuarios activos y preocupados.
|
||||
|
||||
---
|
||||
|
||||
## Eventos propuestos para el futuro (NO implementados aún)
|
||||
|
||||
Esta sección es la **wishlist** para cuando existan los features o lleguen
|
||||
las decisiones pendientes.
|
||||
|
||||
### Cuando exista el plan premium/suscripción
|
||||
- `purchase` / `purchase_subscription` (con `value`, `currency`, `transaction_id`)
|
||||
- `action_click_gopremium` (botón de upgrade)
|
||||
- `subscription_error_payment` / `subscription_canceled_payment`
|
||||
- User property `user_plan` (`free` / `premium` / `family`)
|
||||
|
||||
### Limit popups / free-tier walls
|
||||
- `legacy_limit_hit` con `limit_type` (max_devices, max_contacts, etc.)
|
||||
- `legacy_limit_popup_shown`
|
||||
- `legacy_limit_popup_upgrade_clicked`
|
||||
|
||||
### Referral / invitación
|
||||
- `legacy_referral_screen_viewed`
|
||||
- `legacy_referral_code_shared` (con `channel`)
|
||||
- `legacy_referral_signup_completed`
|
||||
|
||||
### NPS / rating
|
||||
- `legacy_nps_prompt_shown`
|
||||
- `legacy_nps_score_submitted` (con `score` 0–10)
|
||||
- `legacy_app_rating_submitted`
|
||||
|
||||
### Push notification engagement
|
||||
- `legacy_notification_received` (background)
|
||||
- `legacy_notification_opened` (tap app abre)
|
||||
- `legacy_notification_dismissed`
|
||||
|
||||
### Aha moments
|
||||
- `legacy_first_device_connected` (primera vez que el usuario vincula un dispositivo — requiere persistencia de "primera vez")
|
||||
- `legacy_first_session_completed`
|
||||
|
||||
### A/B testing
|
||||
- `ab_test_<experiment_name>` (cuando empecemos experimentos con Remote Config)
|
||||
|
||||
### Errores de API / health técnica
|
||||
- `legacy_api_error` con `endpoint`, `status_code` (detectar endpoints flakey)
|
||||
- `legacy_session_expired`
|
||||
|
||||
---
|
||||
|
||||
## Referencias técnicas
|
||||
|
||||
- **Proyecto Firebase:** `sf-platform-pre` (para dev+staging) / `sf-platform-prod` (pendiente de crear)
|
||||
- **Package Dart:** `packages/sf_tracking/`
|
||||
- **Mixins:** Cada grupo de eventos vive en un mixin aparte dentro del package (`auth_tracking.dart`, `location_tracking.dart`, etc).
|
||||
- **GDPR:** Cada evento incluye automáticamente el parámetro `consent_status` para permitir filtrado post-hoc en BigQuery cuando se implemente el consent screen.
|
||||
- **Ambiente:** `env` se setea como user property (`development` / `staging` / `production`), por lo que **todos los reportes pueden filtrarse por ambiente** y producción no se va a mezclar con testing.
|
||||
|
||||
---
|
||||
|
||||
## Changelog del catálogo
|
||||
|
||||
- **2026-04-07** — Creación inicial. 61 eventos del módulo legacy implementados y validados en device físico (iPhone 14 Pro iOS 18 + Samsung Galaxy A55 Android 15).
|
||||
- **2026-04-07** — Se agregaron 16 eventos nuevos al módulo de ubicación (funnel de creación, exploración, edición, follow mode, map zoom debounced, history).
|
||||
- **2026-04-07** — Se expandió el tracking de `device_management` con 8 eventos nuevos y 3 enriquecimientos de parámetros:
|
||||
- NUEVOS: `legacy_device_locate_success/failure`, `legacy_device_remote_connection_picture_viewed`, `legacy_device_activity_meter_time_range_changed`, `legacy_device_health_time_range_changed`, `legacy_device_apps_use_time_range_changed` (con total_duration_seconds y top_app_name), `legacy_device_call_history_filter_changed`.
|
||||
- ENRIQUECIDOS: `legacy_device_volume_control_changed` ahora dispara un evento por cada tipo (media/ringtone/alarm) que efectivamente cambió; `legacy_device_scheduled_activity_added/updated` ahora incluyen `week_day` y `period`; `legacy_contacts_added/deleted` ahora incluyen `total_count`.
|
||||
- **2026-04-07** — Se expandió el tracking de `settings` con 3 eventos nuevos y 7 enriquecimientos de parámetros:
|
||||
- NUEVOS: `legacy_settings_remote_management_shutdown/restart/factory_reset` (churn signal crítico).
|
||||
- ENRIQUECIDOS: `legacy_settings_alarm_added/updated` ahora incluyen `time`; `legacy_settings_sos_contact_added/removed` incluyen `total_count`; `legacy_settings_block_phone_contact_added/removed` incluyen `total_count`; `legacy_settings_wifi_added/removed` incluyen `total_count`; `legacy_settings_sound_changed` incluye `mode`; `legacy_settings_alerts_configured` incluye `alert_count` y `alerts_enabled`.
|
||||
- **2026-04-07** — Se expandió el tracking de `device_setup` con 3 eventos nuevos y 2 enriquecimientos críticos:
|
||||
- NUEVOS: `legacy_device_setup_qr_scanned`, `legacy_device_setup_manual_code_entered`, `legacy_device_setup_reset_for_new_kid` (señal de familias con múltiples hijos).
|
||||
- ENRIQUECIDOS: `legacy_device_setup_step_completed` ahora incluye `duration_seconds` (tiempo por paso — fricción directa); `legacy_device_setup_completed` ahora incluye `child_gender`, `relation_type`, `child_age_years` ( demográficos del usuario final para personas de marketing).
|
||||
- **2026-04-07** — Se expandió el tracking de `legacy_auth` con 3 eventos nuevos para el funnel interno del signup:
|
||||
- NUEVOS: `legacy_auth_signup_step_completed`, `legacy_auth_signup_step_back`, `legacy_auth_signup_step_validation_failed` (originalmente con `step_index` 0-2; reducido a 0-1 en abril 2026 al simplificar el signup).
|
||||
- **2026-04-07** — Pasada final de cobertura en `legacy_auth`, `account`, `support`, `control_panel`: 6 eventos nuevos y 2 enriquecimientos.
|
||||
- NUEVOS AUTH: `legacy_auth_password_reset_completed`, `legacy_auth_password_reset_failed` (con `reason` granular), `legacy_auth_link_phone_code_request_failed`, `legacy_auth_link_phone_code_verification_failed`.
|
||||
- NUEVOS ACCOUNT: `legacy_account_linked_device_renamed`, `legacy_account_app_user_delete_triggered`.
|
||||
- ENRIQUECIDOS: `legacy_support_contact_initiated` ahora incluye `country` además de `channel`; `legacy_control_panel_device_selected` ahora incluye `total_devices` (proxy de tamaño de familia).
|
||||
- **2026-04-07** — Se expandió la cobertura de los widgets del módulo `location` con 6 eventos nuevos sobre acciones top-level del mapa:
|
||||
- NUEVOS: `legacy_location_map_style_changed` (selector de capas), `legacy_location_map_center_tapped`, `legacy_location_map_refresh_tapped`, `legacy_location_shared` ( acción viral del producto), `legacy_location_list_sheet_opened`, `legacy_location_history_type_filter_changed` (con `type` para detectar interés en posiciones SOS).
|
||||
- **2026-04-15** — Cambios de producto en `legacy_auth`:
|
||||
- **Signup reducido a 2 pasos** (antes 3). Se quitaron los campos de documento, fecha de nacimiento, lugar de nacimiento, país de nacimiento, relación con el niño y dirección completa. El request al backend ahora solo incluye `firstName`, `lastName`, `email`, `phone` (E.164), `language` (del locale del dispositivo) y `password`. `step_index` de los eventos `legacy_auth_signup_step_*` pasa de 0-2 a 0-1.
|
||||
- **Recover password solo por email**: se eliminó la UI de teléfono móvil en ambos screens del flujo (`request_recovery` y `new_password`). Los eventos del flujo se mantienen igual pero ahora siempre corresponden al canal email. Se eliminó del state `recoveryFormat` (ya no hay rama SMS).
|
||||
- **User properties (Firebase Analytics)** ahora se sincronizan solo en shells autenticados (dashboards legacy y payment), no en rutas públicas. Los eventos en sí no cambian — solo se movió el disparador de la sync para evitar llamadas espurias a `/auth/me` en login/signup/recover_password.
|
||||
353
apps/mobile_app/docs/analytics-catalog.md
Normal file
353
apps/mobile_app/docs/analytics-catalog.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Catálogo de Eventos — SaveFamily
|
||||
|
||||
> Documento para el equipo de Marketing. Lista todos los eventos que la app registra y describe el momento exacto en que se dispara cada uno.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [Pantallas de la app](#pantallas-de-la-app)
|
||||
2. [Autenticación](#autenticación)
|
||||
3. [Cuenta](#cuenta)
|
||||
4. [Alta de dispositivo (reloj/wearable del niño)](#alta-de-dispositivo)
|
||||
5. [Funciones del dispositivo](#funciones-del-dispositivo)
|
||||
6. [Contactos del dispositivo](#contactos-del-dispositivo)
|
||||
7. [Ajustes del dispositivo](#ajustes-del-dispositivo)
|
||||
8. [Soporte](#soporte)
|
||||
9. [Onboarding](#onboarding)
|
||||
10. [Panel principal (home)](#panel-principal)
|
||||
11. [Ubicación y mapa](#ubicación-y-mapa)
|
||||
|
||||
---
|
||||
|
||||
## Pantallas de la app
|
||||
|
||||
Cada vez que el usuario navega a una pantalla, queda registrada
|
||||
automáticamente. También se registran los cambios entre pestañas del menú
|
||||
inferior.
|
||||
|
||||
Pantallas registradas:
|
||||
|
||||
- Pantalla de carga inicial
|
||||
- Onboarding / intro
|
||||
- Login
|
||||
- Alta de cuenta (signup)
|
||||
- Recuperación de contraseña
|
||||
- Wizard de alta de reloj/dispositivo
|
||||
- Inicio de vinculación de teléfono
|
||||
- Verificación del código de vinculación
|
||||
- Dashboard principal (home)
|
||||
- Soporte / atención al cliente
|
||||
- Menú de cuenta
|
||||
- Editar datos personales
|
||||
- Cambiar contraseña
|
||||
- Dispositivos vinculados a la cuenta
|
||||
- Sub-usuarios de la app
|
||||
- Eliminación de cuenta
|
||||
- Menú de gestión del dispositivo
|
||||
- Actividades programadas
|
||||
- Contactos
|
||||
- Editar contacto
|
||||
- Salud (ritmo cardíaco, oxígeno en sangre)
|
||||
- Conexión remota (cámara y llamada)
|
||||
- Localizar dispositivo
|
||||
- Recompensas
|
||||
- Medidor de actividad / pasos
|
||||
- Uso de aplicaciones
|
||||
- Control de volumen
|
||||
- Historial de llamadas
|
||||
- Imagen de fondo del dispositivo
|
||||
- Mapa de ubicación
|
||||
- Chat
|
||||
- Menú de ajustes
|
||||
- Alarmas
|
||||
- Gestión remota
|
||||
- Contactos SOS
|
||||
- Sonido del dispositivo
|
||||
- Sincronización del reloj
|
||||
- Gestión de apps instaladas
|
||||
- Batería / modo nocturno
|
||||
- Bloqueo de teléfono (whitelist)
|
||||
- Desactivar funciones (teclado, GPS)
|
||||
- Idioma del dispositivo
|
||||
- Notificaciones del dispositivo
|
||||
- Encendido/apagado remoto
|
||||
- Alertas
|
||||
- Zona horaria
|
||||
- Configuración WiFi
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
### Login
|
||||
|
||||
- **legacy_auth_login_attempt** — El usuario pulsa "Iniciar sesión" después de completar el formulario.
|
||||
- **legacy_auth_login_success** — Email y contraseña aceptados (todavía falta el segundo factor).
|
||||
- **legacy_auth_login_failure** — El intento de login fue rechazado.
|
||||
|
||||
### Doble factor (2FA)
|
||||
|
||||
- **legacy_auth_2fa_requested** — Se le envió el código de verificación al usuario.
|
||||
- **legacy_auth_2fa_verified** — El código fue aceptado y la sesión está activa (login completado).
|
||||
- **legacy_auth_2fa_failure** — El código fue rechazado (incorrecto o expirado).
|
||||
- **legacy_auth_2fa_resend** — El usuario pidió que le reenvíen el código.
|
||||
|
||||
### Alta de cuenta (signup)
|
||||
|
||||
El alta es un wizard de 2 pasos (datos personales → contraseña).
|
||||
|
||||
- **legacy_auth_signup_started** — El usuario envió el formulario final del alta.
|
||||
- **legacy_auth_signup_completed** — La cuenta se creó exitosamente.
|
||||
- **legacy_auth_signup_failed** — El alta falló.
|
||||
- **legacy_auth_signup_step_completed** — El usuario completó un paso del wizard y avanzó al siguiente.
|
||||
- **legacy_auth_signup_step_back** — El usuario volvió al paso anterior dentro del wizard.
|
||||
- **legacy_auth_signup_step_validation_failed** — El usuario intentó avanzar pero el formulario tenía errores.
|
||||
|
||||
### Recuperación de contraseña
|
||||
|
||||
Flujo exclusivo por email (no hay opción de SMS).
|
||||
|
||||
- **legacy_auth_password_reset_requested** — El usuario inició el flujo de "olvidé mi contraseña" tipeando su email.
|
||||
- **legacy_auth_password_reset_email_sent** — Se envió el email con el enlace de recuperación.
|
||||
- **legacy_auth_password_reset_completed** — El usuario guardó exitosamente su nueva contraseña.
|
||||
- **legacy_auth_password_reset_failed** — El intento de guardar la nueva contraseña falló.
|
||||
|
||||
### Vinculación de teléfono
|
||||
|
||||
- **legacy_auth_link_phone_code_requested** — El usuario envió su número y pidió el código.
|
||||
- **legacy_auth_link_phone_code_request_failed** — Falló el envío del código.
|
||||
- **legacy_auth_link_phone_code_verified** — El código fue verificado, número vinculado.
|
||||
- **legacy_auth_link_phone_code_verification_failed** — El código no fue aceptado.
|
||||
|
||||
### Cierre de sesión
|
||||
|
||||
- **legacy_auth_logout** — El usuario cerró sesión.
|
||||
|
||||
---
|
||||
|
||||
## Cuenta
|
||||
|
||||
- **legacy_account_personal_data_edited** — El usuario guardó cambios en sus datos personales (nombre, apellido, teléfono).
|
||||
- **legacy_account_password_changed** — El usuario cambió su contraseña exitosamente.
|
||||
- **legacy_account_password_change_failed** — El cambio de contraseña falló.
|
||||
- **legacy_account_linked_device_unlinked** — El usuario quitó un dispositivo vinculado de su cuenta.
|
||||
- **legacy_account_linked_device_renamed** — El usuario renombró un dispositivo vinculado.
|
||||
- **legacy_account_app_user_delete_triggered** — El usuario tocó "eliminar" en la pantalla de sub-usuarios.
|
||||
- **legacy_account_deletion_initiated** — El usuario entró al flujo de "eliminar cuenta". Señal temprana de churn.
|
||||
- **legacy_account_deletion_confirmed** — El usuario confirmó la eliminación.
|
||||
- **legacy_account_deletion_completed** — La cuenta se eliminó.
|
||||
- **legacy_account_deletion_cancelled** — El usuario canceló antes de confirmar la eliminación.
|
||||
|
||||
---
|
||||
|
||||
## Alta de dispositivo
|
||||
|
||||
Vincular el reloj/dispositivo del niño a la cuenta del adulto.
|
||||
|
||||
### Wizard de alta
|
||||
|
||||
- **legacy_device_setup_started** — El usuario entró al wizard de alta de dispositivo.
|
||||
- **legacy_device_setup_step_completed** — El usuario completó un paso del wizard. Se registra cuánto tiempo tardó en ese paso.
|
||||
- **legacy_device_setup_completed** — El dispositivo quedó vinculado. Se registra género y edad del niño y la relación con el adulto.
|
||||
- **legacy_device_setup_failed** — Falló el alta del dispositivo.
|
||||
- **legacy_device_setup_cancelled** — El usuario abandonó el wizard.
|
||||
|
||||
### Cómo se introdujo el código del reloj
|
||||
|
||||
- **legacy_device_setup_qr_scanned** — El usuario escaneó el código QR del reloj.
|
||||
- **legacy_device_setup_manual_code_entered** — El usuario tipeó el código manualmente.
|
||||
|
||||
### Familias con varios hijos
|
||||
|
||||
- **legacy_device_setup_reset_for_new_kid** — Después de terminar un alta, el usuario eligió "agregar otro hijo".
|
||||
|
||||
---
|
||||
|
||||
## Funciones del dispositivo
|
||||
|
||||
### Localizar dispositivo
|
||||
|
||||
- **legacy_device_locate_requested** — El usuario pulsó el botón de localizar.
|
||||
- **legacy_device_locate_success** — La orden de localizar se envió al dispositivo.
|
||||
- **legacy_device_locate_failure** — La orden de localizar falló.
|
||||
|
||||
### Conexión remota (cámara y llamada)
|
||||
|
||||
- **legacy_device_remote_connection_started** — El usuario abrió la pantalla de conexión remota.
|
||||
- **legacy_device_remote_connection_photo_taken** — El usuario pidió una foto desde la cámara remota.
|
||||
- **legacy_device_remote_connection_call_initiated** — El usuario inició una llamada con el dispositivo.
|
||||
- **legacy_device_remote_connection_picture_viewed** — El usuario navegó entre las fotos tomadas remotamente.
|
||||
|
||||
### Volumen del dispositivo
|
||||
|
||||
- **legacy_device_volume_control_changed** — El usuario guardó un cambio de volumen. Se dispara una vez por cada tipo modificado (multimedia, tono de llamada, alarma).
|
||||
|
||||
### Imagen de fondo del reloj
|
||||
|
||||
- **legacy_device_background_image_changed** — El usuario eligió una imagen existente como fondo.
|
||||
- **legacy_device_background_image_uploaded** — El usuario subió una foto personal como fondo.
|
||||
|
||||
### Actividades programadas (rutinas en el dispositivo)
|
||||
|
||||
- **legacy_device_scheduled_activity_added** — El usuario creó una nueva actividad programada. Se registra el día de la semana y el horario.
|
||||
- **legacy_device_scheduled_activity_updated** — El usuario editó una actividad programada.
|
||||
- **legacy_device_scheduled_activity_removed** — El usuario eliminó una actividad programada.
|
||||
|
||||
### Recompensas
|
||||
|
||||
- **legacy_device_rewards_granted** — El usuario otorgó minutos de recompensa al dispositivo. Se registra la cantidad de minutos.
|
||||
|
||||
### Podómetro / pasos
|
||||
|
||||
- **legacy_device_activity_pedometer_toggled** — El usuario activó o desactivó el contador de pasos.
|
||||
- **legacy_device_activity_meter_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de pasos (hoy, 7 días, 30 días, personalizado).
|
||||
|
||||
### Salud (ritmo cardíaco / oxígeno en sangre)
|
||||
|
||||
- **legacy_device_health_heart_rate_frequency_changed** — El usuario cambió la frecuencia con la que se mide el ritmo cardíaco.
|
||||
- **legacy_device_health_measurement_started** — El usuario inició una medición manual.
|
||||
- **legacy_device_health_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de salud.
|
||||
|
||||
### Uso de aplicaciones del dispositivo
|
||||
|
||||
- **legacy_device_apps_use_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de uso de apps. Se registra el tiempo total acumulado y la app más usada del período.
|
||||
|
||||
### Historial de llamadas
|
||||
|
||||
- **legacy_device_call_history_filter_changed** — El usuario filtró el historial (todas, entrantes, salientes, perdidas). Filtrar perdidas suele ser señal de preocupación del adulto.
|
||||
|
||||
---
|
||||
|
||||
## Contactos del dispositivo
|
||||
|
||||
Contactos permitidos para llamar al/desde el dispositivo del niño.
|
||||
|
||||
- **legacy_contacts_added** — El usuario agregó un contacto. Se registra cuántos contactos tiene en total.
|
||||
- **legacy_contacts_edited** — El usuario editó un contacto existente.
|
||||
- **legacy_contacts_deleted** — El usuario eliminó un contacto. Se registra el total restante.
|
||||
|
||||
---
|
||||
|
||||
## Ajustes del dispositivo
|
||||
|
||||
### Alarmas
|
||||
|
||||
- **legacy_settings_alarm_added** — El usuario creó una alarma. Se registra la hora.
|
||||
- **legacy_settings_alarm_updated** — El usuario editó una alarma.
|
||||
- **legacy_settings_alarm_removed** — El usuario eliminó una alarma.
|
||||
|
||||
### Contactos SOS
|
||||
|
||||
- **legacy_settings_sos_contact_added** — El usuario agregó un contacto SOS.
|
||||
- **legacy_settings_sos_contact_removed** — El usuario eliminó un contacto SOS.
|
||||
|
||||
### Whitelist de llamadas (bloqueo de teléfono)
|
||||
|
||||
- **legacy_settings_block_phone_contact_added** — El usuario agregó un contacto a la lista de llamadas permitidas.
|
||||
- **legacy_settings_block_phone_contact_removed** — El usuario quitó un contacto de la lista de llamadas permitidas.
|
||||
|
||||
### Control parental (funciones desactivadas)
|
||||
|
||||
- **legacy_settings_disable_functions_changed** — El usuario guardó cambios en la pantalla de funciones desactivadas.
|
||||
- **legacy_settings_disable_functions_keyboard_toggled** — El usuario activó o desactivó el teclado.
|
||||
- **legacy_settings_disable_functions_gps_toggled** — El usuario activó o desactivó el GPS.
|
||||
|
||||
### Otros ajustes
|
||||
|
||||
- **legacy_settings_language_changed** — El usuario cambió el idioma del dispositivo.
|
||||
- **legacy_settings_alerts_configured** — El usuario guardó cambios en las alertas. Se registra cuántas alertas activas tiene y cuáles están encendidas.
|
||||
- **legacy_settings_timezone_changed** — El usuario cambió la zona horaria.
|
||||
- **legacy_settings_wifi_added** — El usuario agregó una red WiFi permitida.
|
||||
- **legacy_settings_wifi_removed** — El usuario eliminó una red WiFi permitida.
|
||||
- **legacy_settings_sound_changed** — El usuario cambió el modo de sonido del dispositivo (normal / silencio / vibración).
|
||||
- **legacy_settings_sync_clock_triggered** — El usuario disparó una sincronización manual del reloj.
|
||||
- **legacy_settings_battery_night_mode_toggled** — El usuario activó o desactivó el modo nocturno (ahorro de batería).
|
||||
|
||||
### Gestión remota del dispositivo
|
||||
|
||||
- **legacy_settings_remote_management_shutdown** — El usuario apagó el dispositivo a distancia.
|
||||
- **legacy_settings_remote_management_restart** — El usuario reinició el dispositivo a distancia.
|
||||
- **legacy_settings_remote_management_factory_reset** — El usuario restauró el dispositivo a fábrica. Suele preceder al desvinculado y al churn.
|
||||
|
||||
---
|
||||
|
||||
## Soporte
|
||||
|
||||
- **legacy_support_contact_initiated** — El usuario tocó el botón para contactar a soporte. Se registra el canal (email) y el país seleccionado en el formulario.
|
||||
|
||||
---
|
||||
|
||||
## Onboarding
|
||||
|
||||
- **legacy_onboarding_step_changed** — El usuario pasó a un nuevo slide del intro inicial.
|
||||
|
||||
---
|
||||
|
||||
## Panel principal
|
||||
|
||||
- **legacy_control_panel_device_selected** — El usuario cambió el dispositivo activo (útil cuando hay varios hijos). Se registra cuántos dispositivos tiene vinculados.
|
||||
- **legacy_control_panel_positions_refreshed** — El usuario refrescó manualmente el dashboard (pull-to-refresh o botón de actualizar).
|
||||
|
||||
---
|
||||
|
||||
## Ubicación y mapa
|
||||
|
||||
### Zonas seguras (geofences)
|
||||
|
||||
- **legacy_location_geofence_created** — Se creó una nueva zona segura.
|
||||
- **legacy_location_geofence_updated** — Se editó una zona segura existente.
|
||||
- **legacy_location_geofence_deleted** — Se eliminó una zona segura.
|
||||
|
||||
### Lugares frecuentes
|
||||
|
||||
- **legacy_location_frequent_place_created** — Se creó un nuevo lugar frecuente.
|
||||
- **legacy_location_frequent_place_updated** — Se editó un lugar frecuente existente.
|
||||
- **legacy_location_frequent_place_deleted** — Se eliminó un lugar frecuente.
|
||||
|
||||
### Funnel de creación de lugares (zonas y frecuentes)
|
||||
|
||||
- **legacy_location_place_creation_started** — El usuario tocó "agregar zona" o "agregar lugar frecuente".
|
||||
- **legacy_location_point_confirmed** — El usuario fijó el centro del lugar tocando el mapa.
|
||||
- **legacy_location_radius_confirmed** — El usuario confirmó el radio de la zona segura. Se registra el tamaño del radio.
|
||||
- **legacy_location_place_creation_cancelled** — El usuario abandonó el flujo de creación o edición. Se registra en qué paso lo dejó.
|
||||
|
||||
### Exploración y edición
|
||||
|
||||
- **legacy_location_geofence_selected** — El usuario tocó una zona segura del mapa para verla.
|
||||
- **legacy_location_geofence_dismissed** — El usuario cerró el detalle de la zona sin hacer cambios.
|
||||
- **legacy_location_geofence_edit_started** — El usuario tocó "editar" en una zona seleccionada.
|
||||
- **legacy_location_frequent_place_selected** — El usuario tocó un lugar frecuente para verlo.
|
||||
- **legacy_location_frequent_place_dismissed** — El usuario cerró el detalle del lugar frecuente.
|
||||
- **legacy_location_history_position_selected** — El usuario tocó un punto del historial de ubicaciones en el mapa.
|
||||
- **legacy_location_history_position_dismissed** — El usuario cerró el detalle del punto del historial.
|
||||
|
||||
### Historial de ubicaciones
|
||||
|
||||
- **legacy_location_history_loaded** — El usuario cargó el historial de ubicaciones para un rango de fechas.
|
||||
- **legacy_location_history_cleared** — El usuario limpió el trayecto del mapa.
|
||||
|
||||
### Frecuencia de ubicación
|
||||
|
||||
- **legacy_location_frequency_updated** — El usuario cambió cada cuánto el dispositivo manda su posición.
|
||||
|
||||
### Capas del mapa
|
||||
|
||||
- **legacy_location_map_geofences_toggled** — El usuario mostró u ocultó las zonas seguras en el mapa.
|
||||
- **legacy_location_map_frequent_places_toggled** — El usuario mostró u ocultó los lugares frecuentes en el mapa.
|
||||
- **legacy_location_map_route_trail_toggled** — El usuario mostró u ocultó la línea del trayecto histórico.
|
||||
|
||||
### Modo "seguir en vivo"
|
||||
|
||||
- **legacy_location_following_toggled** — El usuario activó o desactivó el modo "seguir dispositivo" (el mapa se re-centra automáticamente sobre el niño).
|
||||
|
||||
### Acciones del mapa
|
||||
|
||||
- **legacy_location_map_actions_expanded** — El usuario abrió o cerró el menú de acciones del mapa.
|
||||
- **legacy_location_map_zoomed** — El usuario hizo zoom y se quedó en ese nivel.
|
||||
- **legacy_location_map_style_changed** — El usuario eligió otro estilo de mapa (estándar, claro, oscuro, satélite, etc.).
|
||||
- **legacy_location_map_center_tapped** — El usuario tocó "centrar en el dispositivo" para volver el mapa sobre el niño.
|
||||
- **legacy_location_map_refresh_tapped** — El usuario tocó refrescar dentro del mapa para pedir la posición más reciente.
|
||||
- **legacy_location_shared** — El usuario compartió la ubicación del niño hacia otra app (familia, pareja, abuelos). Acción viral del producto.
|
||||
- **legacy_location_list_sheet_opened** — El usuario abrió la lista con todas sus zonas, lugares frecuentes e historial.
|
||||
- **legacy_location_history_type_filter_changed** — El usuario filtró el historial por tipo de posición (GPS, WiFi, SOS). Filtrar SOS suele indicar que está investigando un evento crítico.
|
||||
@@ -1 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:android:b87245b807258e3e5e6317","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:ios:5172d626d02dfe215e6317","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options_dev.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:c3a09d6c26f0cdf95e6317","ios":"1:535646668726:ios:524afa641f61d7cb5e6317"}},"lib/firebase_options_staging.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:b87245b807258e3e5e6317","ios":"1:535646668726:ios:5172d626d02dfe215e6317"}}}}}}
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:android:b87245b807258e3e5e6317","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:ios:5172d626d02dfe215e6317","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options_dev.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:c3a09d6c26f0cdf95e6317","ios":"1:535646668726:ios:524afa641f61d7cb5e6317"}},"lib/firebase_options_staging.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:b87245b807258e3e5e6317","ios":"1:535646668726:ios:5172d626d02dfe215e6317"}},"lib/firebase_options_prod.dart":{"projectId":"sf-platform-pro","configurations":{"android":"1:950566980029:android:75a7c10b6259d09681aad4","ios":"1:950566980029:ios:987b4f0b9e9b897481aad4"}}}}}}
|
||||
@@ -192,6 +192,8 @@ PODS:
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -225,6 +227,7 @@ DEPENDENCIES:
|
||||
- flutter_treezor_entrust_sdk_bridge (from `.symlinks/plugins/flutter_treezor_entrust_sdk_bridge/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
@@ -283,6 +286,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
@@ -329,6 +334,7 @@ SPEC CHECKSUMS:
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
|
||||
@@ -256,6 +256,7 @@
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
F0758EB530B1A8787EB3F30B /* Copy GoogleService-Info */,
|
||||
AA0000022345678900000001 /* Copy AntelopRelease */,
|
||||
437F5EA1E5D92D7C421FD996 /* [CP] Embed Pods Frameworks */,
|
||||
791C3CA41F1AAEE1267769C8 /* [CP] Copy Pods Resources */,
|
||||
0F0F4E82D9AA0B3E11014E72 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
|
||||
@@ -479,6 +480,24 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/scripts/copy-google-service-plist.sh\"";
|
||||
};
|
||||
AA0000022345678900000001 /* Copy AntelopRelease */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy AntelopRelease";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/scripts/copy-antelop-release-plist.sh\"";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>fr.antelop.initialConnectionTimeout</key>
|
||||
<integer>60</integer>
|
||||
<key>fr.antelop.application_id</key>
|
||||
<integer>2940147927882003152</integer>
|
||||
<integer>4713640103500149457</integer>
|
||||
<key>fr.antelop.issuer_id</key>
|
||||
<string>treezor</string>
|
||||
<key>fr.antelop.teamIdentifier</key>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>fr.antelop.initialConnectionTimeout</key>
|
||||
<integer>60</integer>
|
||||
<key>fr.antelop.application_id</key>
|
||||
<integer>2940147927882003152</integer>
|
||||
<integer>4713640103500149457</integer>
|
||||
<key>fr.antelop.issuer_id</key>
|
||||
<string>treezor</string>
|
||||
<key>fr.antelop.teamIdentifier</key>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>fr.antelop.initialConnectionTimeout</key>
|
||||
<integer>60</integer>
|
||||
<key>fr.antelop.application_id</key>
|
||||
<integer>5850886184402974206</integer>
|
||||
<integer>4713640103500149457</integer>
|
||||
<key>fr.antelop.issuer_id</key>
|
||||
<string>treezor</string>
|
||||
<key>fr.antelop.teamIdentifier</key>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyC0_d7Z6uVHHKhaf7JHRROaY6g2mvvpOXU</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>950566980029</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.savefamily.app</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sf-platform-pro</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sf-platform-pro.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:950566980029:ios:987b4f0b9e9b897481aad4</string>
|
||||
</dict>
|
||||
</plist>
|
||||
46
apps/mobile_app/ios/scripts/copy-antelop-release-plist.sh
Executable file
46
apps/mobile_app/ios/scripts/copy-antelop-release-plist.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copies the correct AntelopRelease.plist into the .app bundle based on the
|
||||
# active build CONFIGURATION (Debug-development, Release-staging, etc.). The
|
||||
# Antelop SDK reads `AntelopRelease.plist` by a fixed name at runtime, so the
|
||||
# per-flavor variants (AntelopRelease-development.plist,
|
||||
# AntelopRelease-staging.plist) must be copied over that fixed name.
|
||||
#
|
||||
# Source layout: ios/Runner/AntelopRelease.plist (production),
|
||||
# ios/Runner/AntelopRelease-development.plist,
|
||||
# ios/Runner/AntelopRelease-staging.plist.
|
||||
|
||||
set -e
|
||||
|
||||
echo "Configuration: ${CONFIGURATION}"
|
||||
|
||||
if [[ $CONFIGURATION =~ \-([^-]*)$ ]]; then
|
||||
flavor=${BASH_REMATCH[1]}
|
||||
else
|
||||
echo "warning: Could not extract flavor from CONFIGURATION='${CONFIGURATION}', defaulting to 'production'"
|
||||
flavor="production"
|
||||
fi
|
||||
|
||||
echo "Flavor: $flavor"
|
||||
|
||||
case "$flavor" in
|
||||
development|staging)
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease-${flavor}.plist"
|
||||
;;
|
||||
production)
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease.plist"
|
||||
;;
|
||||
*)
|
||||
echo "warning: Unknown flavor '${flavor}', falling back to AntelopRelease.plist"
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease.plist"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "error: ${SRC} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEST="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/AntelopRelease.plist"
|
||||
echo "Copying ${SRC} -> ${DEST}"
|
||||
cp "${SRC}" "${DEST}"
|
||||
@@ -2,6 +2,7 @@ abstract class Environment {
|
||||
static const env = String.fromEnvironment('env', defaultValue: 'development');
|
||||
static const apiBaseUrl = String.fromEnvironment('apiBaseUrl');
|
||||
static const apiOrigin = String.fromEnvironment('apiOrigin');
|
||||
static const wsUrl = String.fromEnvironment('wsUrl');
|
||||
|
||||
// --- Fase 2: Firebase & Sentry ---
|
||||
// static const sentryDsn = String.fromEnvironment('sentryDsn');
|
||||
|
||||
@@ -6,4 +6,6 @@ class SaveFamilyEnvConfig implements EnvConfig {
|
||||
String get apiBaseUrl => Environment.apiBaseUrl;
|
||||
@override
|
||||
String get apiOrigin => Environment.apiOrigin;
|
||||
@override
|
||||
String get wsUrl => Environment.wsUrl;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'app_version_check.dart';
|
||||
|
||||
Future<void> showAppUpdateDialog(
|
||||
BuildContext context, {
|
||||
required AvailableUpdate result,
|
||||
VoidCallback? onDismiss,
|
||||
VoidCallback? onUpdateTapped,
|
||||
}) {
|
||||
final isForce = result is ForceUpdate;
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: !isForce,
|
||||
builder: (dialogContext) {
|
||||
return PopScope(
|
||||
canPop: !isForce,
|
||||
child: AlertDialog(
|
||||
title: Text(
|
||||
dialogContext.translate(
|
||||
isForce
|
||||
? I18n.appUpdateRequiredTitle
|
||||
: I18n.appUpdateAvailableTitle,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
result.message.isNotEmpty
|
||||
? result.message
|
||||
: dialogContext.translate(
|
||||
isForce
|
||||
? I18n.appUpdateRequiredMessage
|
||||
: I18n.appUpdateAvailableMessage,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (!isForce)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop();
|
||||
onDismiss?.call();
|
||||
},
|
||||
child: Text(dialogContext.translate(I18n.appUpdateLater)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _launchStore(result.storeUrl, onUpdateTapped),
|
||||
child: Text(dialogContext.translate(I18n.appUpdateNow)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchStore(String storeUrl, VoidCallback? onTapped) async {
|
||||
onTapped?.call();
|
||||
try {
|
||||
final uri = Uri.tryParse(storeUrl);
|
||||
if (uri == null) {
|
||||
debugPrint('[AppUpdateDialog] invalid store URL: $storeUrl');
|
||||
return;
|
||||
}
|
||||
final launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
if (!launched) {
|
||||
debugPrint('[AppUpdateDialog] launchUrl returned false for $storeUrl');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[AppUpdateDialog] launchUrl failed: $e');
|
||||
}
|
||||
}
|
||||
123
apps/mobile_app/lib/core/app_version_check/app_update_gate.dart
Normal file
123
apps/mobile_app/lib/core/app_version_check/app_update_gate.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
import 'app_update_dialog.dart';
|
||||
import 'app_version_check.dart';
|
||||
|
||||
class AppUpdateGate extends ConsumerStatefulWidget {
|
||||
const AppUpdateGate({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
ConsumerState<AppUpdateGate> createState() => _AppUpdateGateState();
|
||||
}
|
||||
|
||||
class _AppUpdateGateState extends ConsumerState<AppUpdateGate> {
|
||||
bool _dialogVisible = false;
|
||||
VoidCallback? _pendingRouterListener;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_detachPendingRouterListener();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _detachPendingRouterListener() {
|
||||
final listener = _pendingRouterListener;
|
||||
if (listener != null) {
|
||||
appRouter.routerDelegate.removeListener(listener);
|
||||
_pendingRouterListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isStableRoute() {
|
||||
final path = appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
return path.startsWith(AppRoutes.dashboard) ||
|
||||
path.startsWith(AppRoutes.legacyDashboard);
|
||||
}
|
||||
|
||||
void _onResultEmitted(AppVersionCheckResult result) {
|
||||
if (result is! AvailableUpdate) {
|
||||
_detachPendingRouterListener();
|
||||
return;
|
||||
}
|
||||
if (_dialogVisible) return;
|
||||
|
||||
if (_isStableRoute()) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_show(result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_detachPendingRouterListener();
|
||||
void onChange() {
|
||||
if (!_isStableRoute()) return;
|
||||
_detachPendingRouterListener();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_show(result);
|
||||
});
|
||||
}
|
||||
|
||||
_pendingRouterListener = onChange;
|
||||
appRouter.routerDelegate.addListener(onChange);
|
||||
}
|
||||
|
||||
void _show(AvailableUpdate result) {
|
||||
if (!mounted) return;
|
||||
if (_dialogVisible) return;
|
||||
final ctx = appRouter.routerDelegate.navigatorKey.currentContext;
|
||||
if (ctx == null) return;
|
||||
|
||||
final tracking = ref.read(sfTrackingProvider);
|
||||
final kind = _kindLabel(result);
|
||||
|
||||
tracking.appUpdateDialogShown(
|
||||
kind: kind,
|
||||
latestBuild: result.latestBuild,
|
||||
currentBuild: result.currentBuild,
|
||||
);
|
||||
|
||||
_dialogVisible = true;
|
||||
showAppUpdateDialog(
|
||||
ctx,
|
||||
result: result,
|
||||
onDismiss: () {
|
||||
if (result is SoftUpdate) {
|
||||
tracking.appUpdateDialogDismissed(latestBuild: result.latestBuild);
|
||||
ref.read(appVersionCheckProvider.notifier).markSoftDismissed(result);
|
||||
}
|
||||
},
|
||||
onUpdateTapped: () => tracking.appUpdateCtaTapped(
|
||||
kind: kind,
|
||||
latestBuild: result.latestBuild,
|
||||
),
|
||||
).whenComplete(() {
|
||||
_dialogVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
String _kindLabel(AvailableUpdate result) {
|
||||
return switch (result) {
|
||||
SoftUpdate() => 'soft',
|
||||
ForceUpdate() => 'force',
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen<AsyncValue<AppVersionCheckResult>>(
|
||||
appVersionCheckProvider,
|
||||
(previous, next) {
|
||||
next.whenData(_onResultEmitted);
|
||||
},
|
||||
);
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'app_version_check_result.dart';
|
||||
import 'app_version_check_service.dart';
|
||||
|
||||
export 'app_version_check_result.dart';
|
||||
export 'app_version_check_service.dart';
|
||||
export 'dismissed_build_store.dart';
|
||||
export 'remote_config_reader.dart';
|
||||
|
||||
final appVersionCheckServiceProvider = Provider<AppVersionCheckService>((ref) {
|
||||
return AppVersionCheckService();
|
||||
});
|
||||
|
||||
class AppVersionCheck extends AsyncNotifier<AppVersionCheckResult> {
|
||||
Future<void>? _inflight;
|
||||
|
||||
AppVersionCheckService get _service =>
|
||||
ref.read(appVersionCheckServiceProvider);
|
||||
|
||||
@override
|
||||
Future<AppVersionCheckResult> build() {
|
||||
return _service.check();
|
||||
}
|
||||
|
||||
Future<void> refresh() => _runSerialized(() async {
|
||||
state = AsyncData(await _service.check());
|
||||
});
|
||||
|
||||
Future<void> markSoftDismissed(SoftUpdate result) =>
|
||||
_runSerialized(() async {
|
||||
await _service.markSoftDismissed(result.latestBuild);
|
||||
state = const AsyncData(NoUpdate());
|
||||
});
|
||||
|
||||
Future<void> _runSerialized(Future<void> Function() op) async {
|
||||
final previous = _inflight;
|
||||
final completer = Completer<void>();
|
||||
_inflight = completer.future;
|
||||
try {
|
||||
if (previous != null) {
|
||||
try {
|
||||
await previous;
|
||||
} catch (_) {}
|
||||
}
|
||||
await op();
|
||||
} finally {
|
||||
completer.complete();
|
||||
if (identical(_inflight, completer.future)) {
|
||||
_inflight = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final appVersionCheckProvider =
|
||||
AsyncNotifierProvider<AppVersionCheck, AppVersionCheckResult>(
|
||||
AppVersionCheck.new,
|
||||
);
|
||||
@@ -0,0 +1,39 @@
|
||||
sealed class AppVersionCheckResult {
|
||||
const AppVersionCheckResult();
|
||||
}
|
||||
|
||||
class NoUpdate extends AppVersionCheckResult {
|
||||
const NoUpdate();
|
||||
}
|
||||
|
||||
sealed class AvailableUpdate extends AppVersionCheckResult {
|
||||
const AvailableUpdate({
|
||||
required this.message,
|
||||
required this.storeUrl,
|
||||
required this.latestBuild,
|
||||
required this.currentBuild,
|
||||
});
|
||||
|
||||
final String message;
|
||||
final String storeUrl;
|
||||
final int latestBuild;
|
||||
final int currentBuild;
|
||||
}
|
||||
|
||||
class SoftUpdate extends AvailableUpdate {
|
||||
const SoftUpdate({
|
||||
required super.message,
|
||||
required super.storeUrl,
|
||||
required super.latestBuild,
|
||||
required super.currentBuild,
|
||||
});
|
||||
}
|
||||
|
||||
class ForceUpdate extends AvailableUpdate {
|
||||
const ForceUpdate({
|
||||
required super.message,
|
||||
required super.storeUrl,
|
||||
required super.latestBuild,
|
||||
required super.currentBuild,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'app_version_check_result.dart';
|
||||
import 'dismissed_build_store.dart';
|
||||
import 'remote_config_keys.dart';
|
||||
import 'remote_config_reader.dart';
|
||||
|
||||
typedef CurrentBuildLoader = Future<int> Function();
|
||||
|
||||
Future<int> defaultCurrentBuildLoader() async {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
return int.tryParse(info.buildNumber) ?? 0;
|
||||
}
|
||||
|
||||
class AppVersionCheckService {
|
||||
AppVersionCheckService({
|
||||
RemoteConfigReader? remoteConfig,
|
||||
DismissedBuildStore? dismissedStore,
|
||||
CurrentBuildLoader? currentBuildLoader,
|
||||
bool? isIos,
|
||||
}) : _remoteConfig = remoteConfig ?? FirebaseRemoteConfigReader(),
|
||||
_dismissedStore = dismissedStore ?? SharedPrefsDismissedBuildStore(),
|
||||
_currentBuildLoader = currentBuildLoader ?? defaultCurrentBuildLoader,
|
||||
_isIos = isIos ?? Platform.isIOS;
|
||||
|
||||
final RemoteConfigReader _remoteConfig;
|
||||
final DismissedBuildStore _dismissedStore;
|
||||
final CurrentBuildLoader _currentBuildLoader;
|
||||
final bool _isIos;
|
||||
|
||||
Future<AppVersionCheckResult> check() async {
|
||||
try {
|
||||
final currentBuild = await _currentBuildLoader();
|
||||
|
||||
try {
|
||||
await _remoteConfig.fetchAndActivate();
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] RC fetch failed: $e');
|
||||
}
|
||||
|
||||
final minRequired = _remoteConfig.getInt(RemoteConfigKeys.minRequiredBuild);
|
||||
final latest = _remoteConfig.getInt(RemoteConfigKeys.latestBuild);
|
||||
final forceFlag = _remoteConfig.getBool(RemoteConfigKeys.updateForce);
|
||||
final message = _remoteConfig.getString(RemoteConfigKeys.updateMessage);
|
||||
final storeUrl = _isIos
|
||||
? _remoteConfig.getString(RemoteConfigKeys.updateUrlIos)
|
||||
: _remoteConfig.getString(RemoteConfigKeys.updateUrlAndroid);
|
||||
|
||||
if (forceFlag || currentBuild < minRequired) {
|
||||
return ForceUpdate(
|
||||
message: message,
|
||||
storeUrl: storeUrl,
|
||||
latestBuild: latest,
|
||||
currentBuild: currentBuild,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentBuild < latest) {
|
||||
final dismissedFor = await _dismissedStore.read();
|
||||
if (latest <= dismissedFor) {
|
||||
return const NoUpdate();
|
||||
}
|
||||
return SoftUpdate(
|
||||
message: message,
|
||||
storeUrl: storeUrl,
|
||||
latestBuild: latest,
|
||||
currentBuild: currentBuild,
|
||||
);
|
||||
}
|
||||
|
||||
return const NoUpdate();
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] check failed: $e');
|
||||
return const NoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markSoftDismissed(int latestBuild) async {
|
||||
try {
|
||||
await _dismissedStore.write(latestBuild);
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] markSoftDismissed failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
abstract class DismissedBuildStore {
|
||||
Future<int> read();
|
||||
Future<void> write(int latestBuild);
|
||||
}
|
||||
|
||||
class SharedPrefsDismissedBuildStore implements DismissedBuildStore {
|
||||
static const _key = 'app_update_dismissed_for_latest_build';
|
||||
|
||||
@override
|
||||
Future<int> read() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getInt(_key) ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write(int latestBuild) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_key, latestBuild);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
class RemoteConfigKeys {
|
||||
const RemoteConfigKeys._();
|
||||
|
||||
static const minRequiredBuild = 'min_required_build';
|
||||
static const latestBuild = 'latest_build';
|
||||
static const updateForce = 'update_force';
|
||||
static const updateMessage = 'update_message';
|
||||
static const updateUrlIos = 'update_url_ios';
|
||||
static const updateUrlAndroid = 'update_url_android';
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||
|
||||
abstract class RemoteConfigReader {
|
||||
Future<void> fetchAndActivate();
|
||||
int getInt(String key);
|
||||
bool getBool(String key);
|
||||
String getString(String key);
|
||||
}
|
||||
|
||||
class FirebaseRemoteConfigReader implements RemoteConfigReader {
|
||||
FirebaseRemoteConfigReader([FirebaseRemoteConfig? rc])
|
||||
: _rc = rc ?? FirebaseRemoteConfig.instance;
|
||||
|
||||
final FirebaseRemoteConfig _rc;
|
||||
|
||||
@override
|
||||
Future<void> fetchAndActivate() => _rc.fetchAndActivate();
|
||||
|
||||
@override
|
||||
int getInt(String key) => _rc.getInt(key);
|
||||
|
||||
@override
|
||||
bool getBool(String key) => _rc.getBool(key);
|
||||
|
||||
@override
|
||||
String getString(String key) => _rc.getString(key);
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:firebase_performance/firebase_performance.dart';
|
||||
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
import '../config/env/environment_enum.dart';
|
||||
import '../firebase_options_dev.dart' as dev_options;
|
||||
import '../firebase_options_prod.dart' as prod_options;
|
||||
import '../firebase_options_staging.dart' as staging_options;
|
||||
import 'app_version_check/remote_config_keys.dart';
|
||||
|
||||
Future<void> setupFirebase(EnvironmentEnum env) async {
|
||||
final FirebaseOptions options;
|
||||
@@ -17,20 +20,18 @@ Future<void> setupFirebase(EnvironmentEnum env) async {
|
||||
case EnvironmentEnum.staging:
|
||||
options = staging_options.DefaultFirebaseOptions.currentPlatform;
|
||||
case EnvironmentEnum.production:
|
||||
// TODO: replace with prod_options.DefaultFirebaseOptions.currentPlatform
|
||||
// once the production Firebase project is created.
|
||||
throw UnsupportedError(
|
||||
'Production Firebase project is not configured yet. '
|
||||
'Run `flutterfire configure --project=<prod-project-id>` and import it here.',
|
||||
);
|
||||
options = prod_options.DefaultFirebaseOptions.currentPlatform;
|
||||
}
|
||||
|
||||
await Firebase.initializeApp(options: options);
|
||||
|
||||
// Report crashes in ALL builds (debug + release) so we catch issues during testing too.
|
||||
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
|
||||
// TODO(gdpr): wire `enabled` to real consent once the fix in backlog lands.
|
||||
final crashlytics = FirebaseCrashlyticsService(enabled: true);
|
||||
FlutterError.onError = (details) =>
|
||||
crashlytics.recordFlutterError(details, fatal: true);
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||
crashlytics.recordError(error, stack, fatal: true);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -48,6 +49,20 @@ Future<void> setupFirebase(EnvironmentEnum env) async {
|
||||
: const Duration(hours: 12),
|
||||
),
|
||||
);
|
||||
await remoteConfig.setDefaults(<String, Object>{
|
||||
RemoteConfigKeys.minRequiredBuild: 0,
|
||||
RemoteConfigKeys.latestBuild: 0,
|
||||
RemoteConfigKeys.updateForce: false,
|
||||
RemoteConfigKeys.updateMessage: '',
|
||||
RemoteConfigKeys.updateUrlIos: 'https://apps.apple.com/app/id6759875039',
|
||||
RemoteConfigKeys.updateUrlAndroid:
|
||||
'https://play.google.com/store/apps/details?id=com.savefamily.app',
|
||||
BrandLinksKeys.privacyPolicyUrl:
|
||||
'https://savefamilygps.com/pages/politica-de-privacidad-reloj-gps-infantil-localizador-savefamily',
|
||||
BrandLinksKeys.corporateWebsiteUrl: 'https://www.savefamilygps.com/',
|
||||
BrandLinksKeys.helpCenterUrl: 'https://savefamilygpshelp.zendesk.com/hc/es',
|
||||
BrandLinksKeys.supportEmail: 'info@savefamilygps.com',
|
||||
});
|
||||
try {
|
||||
await remoteConfig.fetchAndActivate();
|
||||
} catch (e) {
|
||||
|
||||
@@ -3,7 +3,9 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:sca_treezor/sca_treezor.dart';
|
||||
import 'package:sf_app_platform/config/env/environment_enum.dart';
|
||||
import 'package:sf_app_platform/config/env/save_family_env_config.dart';
|
||||
@@ -15,23 +17,27 @@ import 'package:sf_app_platform/save_family_app.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
|
||||
Future<void> initApp(EnvironmentEnum env) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
await initializeDateFormatting();
|
||||
tz.initializeTimeZones();
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
navigationModule();
|
||||
scaTreezorModule();
|
||||
themePackages();
|
||||
|
||||
// Order matters: Firebase → sfTracking (FirebaseTrackingClient touches
|
||||
// FirebaseAnalytics.instance) → router (SaveFamilyApp wires sfTracking
|
||||
// into SfRouterListener at construction time).
|
||||
await setupFirebase(env);
|
||||
await setupNotifications();
|
||||
initSfTracking();
|
||||
|
||||
configureAppRouter();
|
||||
onRouterReady();
|
||||
|
||||
// TODO Fase 2: await initSentry(env);
|
||||
|
||||
@@ -40,7 +46,7 @@ Future<void> initApp(EnvironmentEnum env) async {
|
||||
log: env.isDevelopment || kDebugMode,
|
||||
onTokenExpired: isPaymentMode
|
||||
? () => appRouter.go(AppRoutes.scaTreezor)
|
||||
: () => appRouter.go(AppRoutes.legacyLogin),
|
||||
: null,
|
||||
onUnauthorized: () async {
|
||||
final currentLocation =
|
||||
appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
@@ -53,5 +59,12 @@ Future<void> initApp(EnvironmentEnum env) async {
|
||||
},
|
||||
);
|
||||
|
||||
runApp(const ProviderScope(child: SaveFamilyApp()));
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
|
||||
],
|
||||
child: const SaveFamilyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,14 @@ import 'dart:convert';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
|
||||
/// Background message handler. MUST be a top-level function annotated with
|
||||
/// `@pragma('vm:entry-point')` so the Flutter engine can dispatch it from a
|
||||
/// background isolate when the app is terminated or backgrounded.
|
||||
///
|
||||
/// This runs in a separate isolate: it CANNOT access main-isolate state
|
||||
/// (providers, GetIt, navigation). Keep it side-effect free or schedule work
|
||||
/// via shared_preferences. Do not call `Firebase.initializeApp` here —
|
||||
/// firebase_messaging 14+ auto-initializes the default app for the background
|
||||
/// isolate.
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
debugPrint(
|
||||
'[FCM-bg] message received: ${message.messageId} - ${message.notification?.title}',
|
||||
);
|
||||
debugPrint('[FCM-bg] messageId: ${message.messageId}');
|
||||
debugPrint('[FCM-bg] notification: title=${message.notification?.title}, body=${message.notification?.body}');
|
||||
debugPrint('[FCM-bg] data: ${message.data}');
|
||||
}
|
||||
|
||||
const String _localChannelId = 'sf_default_channel';
|
||||
@@ -28,6 +21,9 @@ const String _localChannelDescription =
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
Map<String, dynamic>? _pendingNotificationData;
|
||||
bool _routerReady = false;
|
||||
|
||||
Future<void> setupNotifications() async {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
|
||||
@@ -56,11 +52,6 @@ Future<void> setupNotifications() async {
|
||||
_onMessageOpenedApp(initialMessage);
|
||||
}
|
||||
|
||||
// TODO: integrate with backend (Treezor/SaveFamily api).
|
||||
messaging.onTokenRefresh.listen((newToken) {
|
||||
debugPrint('[FCM] token refreshed: $newToken');
|
||||
});
|
||||
|
||||
try {
|
||||
final token = await messaging.getToken();
|
||||
debugPrint('[FCM] initial token: $token');
|
||||
@@ -69,10 +60,19 @@ Future<void> setupNotifications() async {
|
||||
}
|
||||
}
|
||||
|
||||
void onRouterReady() {
|
||||
_routerReady = true;
|
||||
final pending = _pendingNotificationData;
|
||||
if (pending != null) {
|
||||
_pendingNotificationData = null;
|
||||
_handleNotificationNavigation(pending);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initLocalNotifications() async {
|
||||
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const iosInit = DarwinInitializationSettings(
|
||||
requestAlertPermission: false, // already requested via FirebaseMessaging
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
);
|
||||
@@ -86,7 +86,6 @@ Future<void> _initLocalNotifications() async {
|
||||
onDidReceiveNotificationResponse: _onLocalNotificationTapped,
|
||||
);
|
||||
|
||||
// Android 8+ requires every notification to belong to a channel.
|
||||
const channel = AndroidNotificationChannel(
|
||||
_localChannelId,
|
||||
_localChannelName,
|
||||
@@ -101,9 +100,9 @@ Future<void> _initLocalNotifications() async {
|
||||
}
|
||||
|
||||
void _onForegroundMessage(RemoteMessage message) {
|
||||
debugPrint(
|
||||
'[FCM-fg] message received: ${message.messageId} - ${message.notification?.title}',
|
||||
);
|
||||
debugPrint('[FCM-fg] messageId: ${message.messageId}');
|
||||
debugPrint('[FCM-fg] notification: title=${message.notification?.title}, body=${message.notification?.body}');
|
||||
debugPrint('[FCM-fg] data: ${message.data}');
|
||||
|
||||
final notification = message.notification;
|
||||
if (notification == null) return;
|
||||
@@ -133,15 +132,49 @@ void _onForegroundMessage(RemoteMessage message) {
|
||||
}
|
||||
|
||||
void _onMessageOpenedApp(RemoteMessage message) {
|
||||
debugPrint(
|
||||
'[FCM-tap] user tapped notification: ${message.messageId} - data: ${message.data}',
|
||||
);
|
||||
// TODO: handle deep linking based on message.data.
|
||||
debugPrint('[FCM-tap] messageId: ${message.messageId}');
|
||||
debugPrint('[FCM-tap] notification: title=${message.notification?.title}, body=${message.notification?.body}');
|
||||
debugPrint('[FCM-tap] data: ${message.data}');
|
||||
_handleNotificationNavigation(message.data);
|
||||
}
|
||||
|
||||
void _onLocalNotificationTapped(NotificationResponse response) {
|
||||
debugPrint(
|
||||
'[FCM-localtap] user tapped local notification: id=${response.id} payload=${response.payload}',
|
||||
);
|
||||
// TODO: handle deep linking. Payload contains JSON-encoded message.data.
|
||||
debugPrint('[LocalNotif-tap] id=${response.id}');
|
||||
debugPrint('[LocalNotif-tap] payload=${response.payload}');
|
||||
debugPrint('[LocalNotif-tap] actionId=${response.actionId}');
|
||||
|
||||
final payload = response.payload;
|
||||
if (payload == null || payload.isEmpty) return;
|
||||
|
||||
try {
|
||||
final data = jsonDecode(payload) as Map<String, dynamic>;
|
||||
_handleNotificationNavigation(data);
|
||||
} catch (e) {
|
||||
debugPrint('[LocalNotif-tap] failed to parse payload: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleNotificationNavigation(Map<String, dynamic> data) {
|
||||
if (!_routerReady) {
|
||||
_pendingNotificationData = data;
|
||||
debugPrint('[Notification] router not ready, queued for later');
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLocation =
|
||||
appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
if (!currentLocation.startsWith(AppRoutes.legacyDashboard)) return;
|
||||
|
||||
final command = data['command'] as String?;
|
||||
|
||||
switch (command) {
|
||||
case 'ALERT':
|
||||
// TODO(backend): include `alertType` in the push payload so we can deep-link
|
||||
// to the filtered feed via `${AppRoutes.deviceNotifications}?type=<alertType>`
|
||||
// and have NotificationsScreen pre-select `notificationsFilterProvider`.
|
||||
// Until then, land on the category picker ("all").
|
||||
appRouter.go(AppRoutes.deviceNotifications);
|
||||
default:
|
||||
debugPrint('[Notification] unhandled command: $command');
|
||||
}
|
||||
}
|
||||
|
||||
68
apps/mobile_app/lib/firebase_options_prod.dart
Normal file
68
apps/mobile_app/lib/firebase_options_prod.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options_prod.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDkjNdOAK0ype7wgdgiC1BCKV_pP4s_mlA',
|
||||
appId: '1:950566980029:android:75a7c10b6259d09681aad4',
|
||||
messagingSenderId: '950566980029',
|
||||
projectId: 'sf-platform-pro',
|
||||
storageBucket: 'sf-platform-pro.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyC0_d7Z6uVHHKhaf7JHRROaY6g2mvvpOXU',
|
||||
appId: '1:950566980029:ios:987b4f0b9e9b897481aad4',
|
||||
messagingSenderId: '950566980029',
|
||||
projectId: 'sf-platform-pro',
|
||||
storageBucket: 'sf-platform-pro.firebasestorage.app',
|
||||
iosBundleId: 'com.savefamily.app',
|
||||
);
|
||||
}
|
||||
@@ -18,10 +18,30 @@ import 'package:payments/payments.dart';
|
||||
import 'package:profile/profile.dart';
|
||||
import 'package:settings/settings.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
import 'package:sf_app_platform/widgets/user_identity_listener.dart';
|
||||
import 'package:splash/splash.dart';
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
final _legacyControlPanelNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyControlPanel');
|
||||
final _legacyDeviceMgmtNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyDeviceMgmt');
|
||||
final _legacyLocationNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyLocation');
|
||||
final _legacyChatNavKey = GlobalKey<NavigatorState>(debugLabel: 'legacyChat');
|
||||
final _legacySettingsNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacySettings');
|
||||
|
||||
final _dashboardHomeNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardHome');
|
||||
final _dashboardActivityNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardActivity');
|
||||
final _dashboardNotificationsNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardNotifications');
|
||||
final _dashboardProfileNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardProfile');
|
||||
|
||||
late final GoRouter appRouter;
|
||||
|
||||
/// Maps the splash's session check result to the destination route based
|
||||
@@ -31,12 +51,14 @@ const _legacySplashRouteMap = <InitialRoute, String>{
|
||||
InitialRoute.onboarding: AppRoutes.legacyOnboarding,
|
||||
InitialRoute.login: AppRoutes.legacyLogin,
|
||||
InitialRoute.home: AppRoutes.controlPanel,
|
||||
InitialRoute.deviceSetup: AppRoutes.legacyDeviceSetup,
|
||||
};
|
||||
|
||||
const _paymentSplashRouteMap = <InitialRoute, String>{
|
||||
InitialRoute.onboarding: AppRoutes.onboarding,
|
||||
InitialRoute.login: AppRoutes.login,
|
||||
InitialRoute.home: AppRoutes.dashboardHome,
|
||||
InitialRoute.deviceSetup: AppRoutes.deviceSetup,
|
||||
};
|
||||
|
||||
void configureAppRouter() {
|
||||
@@ -56,10 +78,13 @@ void configureAppRouter() {
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navShell) {
|
||||
return LegacyDashboardBuilder().build(context, navShell);
|
||||
return UserIdentityListener(
|
||||
child: LegacyDashboardBuilder().build(context, navShell),
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyControlPanelNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.controlPanel,
|
||||
@@ -71,6 +96,11 @@ void configureAppRouter() {
|
||||
name: 'customer_service',
|
||||
pageBuilder: CustomerServiceBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'notifications',
|
||||
name: 'legacy_notifications',
|
||||
pageBuilder: const DeviceNotificationsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'account_settings',
|
||||
name: 'account_settings',
|
||||
@@ -108,6 +138,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyDeviceMgmtNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.deviceManagement,
|
||||
@@ -166,6 +197,11 @@ void configureAppRouter() {
|
||||
name: 'volume_control',
|
||||
pageBuilder: const VolumeControlBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'do_not_disturb',
|
||||
name: 'do_not_disturb',
|
||||
pageBuilder: const DoNotDisturbBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'call_history',
|
||||
name: 'call_history',
|
||||
@@ -176,11 +212,25 @@ void configureAppRouter() {
|
||||
name: 'background_image',
|
||||
pageBuilder: const BackgroundImageBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'installed_apps',
|
||||
name: 'installed_apps',
|
||||
pageBuilder: const InstalledAppsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'schedules',
|
||||
name: 'app_usage_schedules',
|
||||
pageBuilder:
|
||||
const AppUsageSchedulesBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyLocationNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyLocation,
|
||||
@@ -191,6 +241,7 @@ void configureAppRouter() {
|
||||
),
|
||||
// TODO: Añadir branch para Chat (tab 4)
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyChatNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '${AppRoutes.legacyDashboard}/chat',
|
||||
@@ -205,12 +256,18 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacySettingsNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.settings,
|
||||
name: 'settings',
|
||||
pageBuilder: SettingsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'appearance',
|
||||
name: 'appearance',
|
||||
pageBuilder: AppearanceBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'alarm',
|
||||
name: 'alarm',
|
||||
@@ -261,11 +318,6 @@ void configureAppRouter() {
|
||||
name: 'language',
|
||||
pageBuilder: LanguageBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'legacy_notifications',
|
||||
name: 'legacy_notifications',
|
||||
pageBuilder: LegacyNotificationsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'remote_on_off',
|
||||
name: 'remote_on_off',
|
||||
@@ -376,10 +428,13 @@ void configureAppRouter() {
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navShell) {
|
||||
return DashboardBuilder().build(context, navShell);
|
||||
return UserIdentityListener(
|
||||
child: DashboardBuilder().build(context, navShell),
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardHomeNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardHome,
|
||||
@@ -448,6 +503,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardActivityNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardActivity,
|
||||
@@ -457,6 +513,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardNotificationsNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardNotifications,
|
||||
@@ -466,6 +523,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardProfileNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardProfile,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:auth/auth.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/app_update_gate.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/app_version_check.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
@@ -29,6 +33,7 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
WalletHeartbeatService? _walletHeartbeat;
|
||||
LegacyHeartbeatService? _legacyHeartbeat;
|
||||
SfRouterListener? _trackingRouterListener;
|
||||
WebSocketService? _webSocket;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -61,12 +66,17 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
appRouter.go(AppRoutes.legacyLogin);
|
||||
},
|
||||
);
|
||||
_webSocket = GetIt.I<WebSocketService>();
|
||||
appRouter.routerDelegate.addListener(_onRouteChanged);
|
||||
}
|
||||
|
||||
onBeforeSessionCleared = () {
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
FirebaseMessaging.instance.deleteToken().catchError((Object e) {
|
||||
debugPrint('[FCM] deleteToken on logout failed: $e');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,8 +87,10 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
final location = appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
if (location.startsWith(AppRoutes.legacyDashboard)) {
|
||||
heartbeat.start();
|
||||
_webSocket?.connect();
|
||||
} else {
|
||||
heartbeat.stop();
|
||||
_webSocket?.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +102,7 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
_trackingRouterListener?.dispose();
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
@@ -104,9 +117,11 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
_onRouteChanged();
|
||||
}
|
||||
ref.read(permissionsProvider.notifier).checkPermissions();
|
||||
ref.read(appVersionCheckProvider.notifier).refresh();
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
}
|
||||
super.didChangeAppLifecycleState(state);
|
||||
}
|
||||
@@ -114,45 +129,53 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeUtils.init(context: context);
|
||||
ref.watch(pushTokenRefreshListenerProvider);
|
||||
|
||||
ref.listen<AsyncValue<UserEntity>>(userInfoProvider, (previous, next) {
|
||||
next.whenData((user) {
|
||||
UserInfoTrackingListener(ref.read(sfTrackingProvider)).onUserChanged(
|
||||
userId: user.id,
|
||||
role: user.role,
|
||||
language: user.language,
|
||||
createdAtMillis: user.createdAt,
|
||||
hasPhone: user.phone.isNotEmpty,
|
||||
hasApiKey: user.hasApiKey,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'SaveFamily',
|
||||
theme: ThemeData(
|
||||
// Theme wiring:
|
||||
// - Legacy mode: new `legacy_theme` package (Material 3 + light/dark/system).
|
||||
// - Payment mode: unchanged behaviour (seed-based ColorScheme, light only).
|
||||
final ThemeData lightTheme;
|
||||
final ThemeData? darkTheme;
|
||||
final ThemeMode themeMode;
|
||||
if (isLegacyMode) {
|
||||
final legacyThemeState = ref.watch(legacyThemeNotifierProvider);
|
||||
lightTheme = LegacyAppTheme.light;
|
||||
darkTheme = LegacyAppTheme.dark;
|
||||
themeMode = legacyThemeState.themeMode;
|
||||
} else {
|
||||
lightTheme = ThemeData(
|
||||
fontFamily: AppFonts.stolzl,
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF329E95)),
|
||||
),
|
||||
routerConfig: appRouter,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: [
|
||||
// CountryLocalizations.delegate,
|
||||
SFLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [for (final lang in supportedLanguages) Locale(lang)],
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
if (locale == null) return supportedLocales.first;
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return supportedLocale;
|
||||
);
|
||||
darkTheme = null;
|
||||
themeMode = ThemeMode.light;
|
||||
}
|
||||
|
||||
return AppUpdateGate(
|
||||
child: MaterialApp.router(
|
||||
title: 'SaveFamily',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
routerConfig: appRouter,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: [
|
||||
SFLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [for (final lang in supportedLanguages) Locale(lang)],
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
if (locale == null) return supportedLocales.first;
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return supportedLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supportedLocales.first;
|
||||
},
|
||||
return supportedLocales.first;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
28
apps/mobile_app/lib/widgets/user_identity_listener.dart
Normal file
28
apps/mobile_app/lib/widgets/user_identity_listener.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
class UserIdentityListener extends ConsumerWidget {
|
||||
final Widget child;
|
||||
|
||||
const UserIdentityListener({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.listen<AsyncValue<UserEntity>>(userInfoProvider, (_, next) {
|
||||
next.whenData((user) {
|
||||
UserInfoTrackingListener(ref.read(sfTrackingProvider)).onUserChanged(
|
||||
userId: user.id,
|
||||
role: user.role,
|
||||
language: user.language,
|
||||
createdAtMillis: user.createdAt,
|
||||
hasPhone: user.phone.isNotEmpty,
|
||||
hasApiKey: user.hasApiKey,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ resolution: workspace
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+7
|
||||
version: 1.0.0+9
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
@@ -68,6 +68,8 @@ dependencies:
|
||||
path: ../../modules/legacy/modules/legacy_auth
|
||||
settings:
|
||||
path: ../../modules/legacy/modules/settings
|
||||
legacy_theme:
|
||||
path: ../../modules/legacy/packages/legacy_theme
|
||||
#packages dependencies go here
|
||||
navigation:
|
||||
path: ../../packages/navigation
|
||||
@@ -94,6 +96,8 @@ dependencies:
|
||||
#dependencies go here
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_svg: ^2.2.2
|
||||
intl: ^0.20.2
|
||||
timezone: ^0.10.1
|
||||
go_router_builder: ^4.1.1
|
||||
build_runner: ^2.7.1
|
||||
|
||||
@@ -106,6 +110,9 @@ dependencies:
|
||||
firebase_crashlytics: ^5.1.0
|
||||
firebase_analytics: ^12.2.0
|
||||
firebase_remote_config: ^6.3.0
|
||||
package_info_plus: ^8.3.1
|
||||
url_launcher: ^6.3.2
|
||||
shared_preferences: ^2.5.5
|
||||
firebase_messaging: ^16.1.3
|
||||
firebase_performance: ^0.11.2
|
||||
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/app_version_check.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/remote_config_keys.dart';
|
||||
|
||||
class _FakeRemoteConfig implements RemoteConfigReader {
|
||||
_FakeRemoteConfig({
|
||||
this.minRequired = 0,
|
||||
this.latest = 0,
|
||||
this.force = false,
|
||||
this.message = '',
|
||||
this.iosUrl = 'ios-url',
|
||||
this.androidUrl = 'android-url',
|
||||
this.fetchThrows = false,
|
||||
});
|
||||
|
||||
int minRequired;
|
||||
int latest;
|
||||
bool force;
|
||||
String message;
|
||||
String iosUrl;
|
||||
String androidUrl;
|
||||
bool fetchThrows;
|
||||
int fetchCallCount = 0;
|
||||
|
||||
@override
|
||||
Future<void> fetchAndActivate() async {
|
||||
fetchCallCount++;
|
||||
if (fetchThrows) throw Exception('boom');
|
||||
}
|
||||
|
||||
@override
|
||||
int getInt(String key) => switch (key) {
|
||||
RemoteConfigKeys.minRequiredBuild => minRequired,
|
||||
RemoteConfigKeys.latestBuild => latest,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
@override
|
||||
bool getBool(String key) =>
|
||||
key == RemoteConfigKeys.updateForce ? force : false;
|
||||
|
||||
@override
|
||||
String getString(String key) => switch (key) {
|
||||
RemoteConfigKeys.updateMessage => message,
|
||||
RemoteConfigKeys.updateUrlIos => iosUrl,
|
||||
RemoteConfigKeys.updateUrlAndroid => androidUrl,
|
||||
_ => '',
|
||||
};
|
||||
}
|
||||
|
||||
class _FakeDismissedStore implements DismissedBuildStore {
|
||||
_FakeDismissedStore({this.value = 0, this.writeThrows = false});
|
||||
|
||||
int value;
|
||||
bool writeThrows;
|
||||
int writeCallCount = 0;
|
||||
|
||||
@override
|
||||
Future<int> read() async => value;
|
||||
|
||||
@override
|
||||
Future<void> write(int latestBuild) async {
|
||||
writeCallCount++;
|
||||
if (writeThrows) throw Exception('write boom');
|
||||
value = latestBuild;
|
||||
}
|
||||
}
|
||||
|
||||
AppVersionCheckService _buildService({
|
||||
required _FakeRemoteConfig rc,
|
||||
required _FakeDismissedStore store,
|
||||
required int currentBuild,
|
||||
bool isIos = false,
|
||||
}) {
|
||||
return AppVersionCheckService(
|
||||
remoteConfig: rc,
|
||||
dismissedStore: store,
|
||||
currentBuildLoader: () async => currentBuild,
|
||||
isIos: isIos,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('AppVersionCheckService.check', () {
|
||||
test('returns NoUpdate when current build matches latest and no force',
|
||||
() async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 7),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<NoUpdate>());
|
||||
});
|
||||
|
||||
test('returns NoUpdate when nothing requires attention', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<NoUpdate>());
|
||||
});
|
||||
|
||||
test('returns SoftUpdate when current build is behind latest', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 8, message: 'New stuff'),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<SoftUpdate>());
|
||||
final soft = result as SoftUpdate;
|
||||
expect(soft.latestBuild, 8);
|
||||
expect(soft.currentBuild, 7);
|
||||
expect(soft.message, 'New stuff');
|
||||
});
|
||||
|
||||
test('returns ForceUpdate when current build is below min required',
|
||||
() async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(minRequired: 8, latest: 8),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<ForceUpdate>());
|
||||
final force = result as ForceUpdate;
|
||||
expect(force.latestBuild, 8);
|
||||
expect(force.currentBuild, 7);
|
||||
});
|
||||
|
||||
test('returns ForceUpdate when force flag is true even with current build matching',
|
||||
() async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(force: true, latest: 7),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<ForceUpdate>());
|
||||
});
|
||||
|
||||
test('force flag wins over soft when both conditions are met', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(force: true, latest: 8),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<ForceUpdate>());
|
||||
});
|
||||
|
||||
test('returns NoUpdate when latest is dismissed', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 8),
|
||||
store: _FakeDismissedStore(value: 8),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<NoUpdate>());
|
||||
});
|
||||
|
||||
test('returns NoUpdate when dismissed value is greater than latest',
|
||||
() async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 8),
|
||||
store: _FakeDismissedStore(value: 10),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<NoUpdate>());
|
||||
});
|
||||
|
||||
test('returns SoftUpdate when latest is greater than dismissed', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 9),
|
||||
store: _FakeDismissedStore(value: 8),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<SoftUpdate>());
|
||||
});
|
||||
|
||||
test('dismiss persistence does NOT block force update', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(force: true),
|
||||
store: _FakeDismissedStore(value: 9999),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<ForceUpdate>());
|
||||
});
|
||||
|
||||
test('uses iOS store URL when isIos is true', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 8, iosUrl: 'apple', androidUrl: 'play'),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
isIos: true,
|
||||
);
|
||||
|
||||
final result = await service.check() as SoftUpdate;
|
||||
|
||||
expect(result.storeUrl, 'apple');
|
||||
});
|
||||
|
||||
test('uses Android store URL when isIos is false', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(latest: 8, iosUrl: 'apple', androidUrl: 'play'),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
isIos: false,
|
||||
);
|
||||
|
||||
final result = await service.check() as SoftUpdate;
|
||||
|
||||
expect(result.storeUrl, 'play');
|
||||
});
|
||||
|
||||
test('returns NoUpdate when fetchAndActivate throws', () async {
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(fetchThrows: true, latest: 8),
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<SoftUpdate>(),
|
||||
reason:
|
||||
'fetch failure should fall back to cached values, not return none');
|
||||
});
|
||||
|
||||
test('check still calls fetchAndActivate', () async {
|
||||
final rc = _FakeRemoteConfig();
|
||||
final service = _buildService(
|
||||
rc: rc,
|
||||
store: _FakeDismissedStore(),
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
await service.check();
|
||||
|
||||
expect(rc.fetchCallCount, 1);
|
||||
});
|
||||
|
||||
test('returns NoUpdate when current build loader throws', () async {
|
||||
final service = AppVersionCheckService(
|
||||
remoteConfig: _FakeRemoteConfig(),
|
||||
dismissedStore: _FakeDismissedStore(),
|
||||
currentBuildLoader: () async => throw Exception('package_info crash'),
|
||||
);
|
||||
|
||||
final result = await service.check();
|
||||
|
||||
expect(result, isA<NoUpdate>());
|
||||
});
|
||||
});
|
||||
|
||||
group('AppVersionCheckService.markSoftDismissed', () {
|
||||
test('writes the latest build to the dismiss store', () async {
|
||||
final store = _FakeDismissedStore();
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(),
|
||||
store: store,
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
await service.markSoftDismissed(8);
|
||||
|
||||
expect(store.value, 8);
|
||||
expect(store.writeCallCount, 1);
|
||||
});
|
||||
|
||||
test('swallows write errors silently', () async {
|
||||
final store = _FakeDismissedStore(writeThrows: true);
|
||||
final service = _buildService(
|
||||
rc: _FakeRemoteConfig(),
|
||||
store: store,
|
||||
currentBuild: 7,
|
||||
);
|
||||
|
||||
await expectLater(service.markSoftDismissed(8), completes);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -71,10 +71,12 @@ dependencies:
|
||||
l10n_countries: ^1.3.1
|
||||
sealed_countries: ^2.8.0
|
||||
country_code_picker: ^3.4.1
|
||||
phone_numbers_parser: ^9.0.3
|
||||
|
||||
# ---------------- Utilities ----------------
|
||||
uuid: ^4.5.3
|
||||
plugin_platform_interface: ^2.0.2
|
||||
package_info_plus: ^8.3.1
|
||||
|
||||
dev_dependencies:
|
||||
# ---------------- Linting ----------------
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:account/src/core/data/models/change_password_request_model.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:account/src/core/data/models/change_password_request_dto.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
import '../../../features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
@@ -17,11 +15,7 @@ class ChangePasswordRemoteDatasourceImpl
|
||||
required String userId,
|
||||
required ChangePasswordRequestEntity request,
|
||||
}) async {
|
||||
try {
|
||||
final body = request.toModel().toJson();
|
||||
await _repository.put<void>('/auth/change-password', body: body);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to change password');
|
||||
}
|
||||
final body = request.toDto().toJson();
|
||||
await _repository.put<void>('/auth/change-password', body: body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:account/src/features/linked_devices/domain/entities/update_device_request_entity.dart';
|
||||
import 'package:sf_shared/sf_shared.dart' show DeviceEntity;
|
||||
|
||||
abstract class DevicesRemoteDatasource {
|
||||
Future<void> deleteDevice({required String deviceId});
|
||||
|
||||
Future<void> updateDevice({required UpdateDeviceRequestEntity request});
|
||||
Future<void> updateDevice({required DeviceEntity device});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:account/src/core/data/models/update_device_request_model.dart';
|
||||
import 'package:account/src/features/linked_devices/domain/entities/update_device_request_entity.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/sf_shared.dart' show DeviceEntity;
|
||||
|
||||
import 'devices_remote_datasource.dart';
|
||||
|
||||
@@ -13,22 +11,15 @@ class DevicesRemoteDatasourceImpl implements DevicesRemoteDatasource {
|
||||
|
||||
@override
|
||||
Future<void> deleteDevice({required String deviceId}) async {
|
||||
try {
|
||||
await _repository.delete<void>('/devices/$deviceId');
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to delete device');
|
||||
}
|
||||
await _repository.delete<void>('/devices/$deviceId');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateDevice({
|
||||
required UpdateDeviceRequestEntity request,
|
||||
}) async {
|
||||
try {
|
||||
final body = request.toModel().toJson();
|
||||
await _repository.put<void>('/devices', body: body);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to update device');
|
||||
}
|
||||
Future<void> updateDevice({required DeviceEntity device}) async {
|
||||
final csvBase64 = DeviceCsvBuilder.buildBase64Csv(
|
||||
device: device,
|
||||
settings: device.settings,
|
||||
);
|
||||
await _repository.put<void>('/devices', body: {'csv': csvBase64});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:account/src/core/data/datasource/users_remote_datasource.dart';
|
||||
import 'package:account/src/core/data/models/get_app_users_response_model.dart';
|
||||
import 'package:account/src/core/data/models/update_user_request_model.dart';
|
||||
import 'package:account/src/core/data/models/get_app_users_response_dto.dart';
|
||||
import 'package:account/src/core/data/models/update_user_request_dto.dart';
|
||||
import 'package:account/src/features/personal_data/domain/entities/update_user_request_entity.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
@@ -14,23 +12,16 @@ class UsersRemoteDatasourceImpl implements UsersRemoteDatasource {
|
||||
|
||||
@override
|
||||
Future<List<UserEntity>> getUsers({required String userId}) async {
|
||||
try {
|
||||
final response = await _repository.get<Map<String, dynamic>>(
|
||||
'/users/$userId/user-devices',
|
||||
);
|
||||
final data = response.data!['items'];
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty response from /users/$userId/user-devices');
|
||||
}
|
||||
|
||||
final model = GetAppUsersResponseModel.fromJson(data);
|
||||
return model.toEntity();
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(
|
||||
error,
|
||||
defaultMessage: error.message ?? 'Error getting devices',
|
||||
);
|
||||
final response = await _repository.get<Map<String, dynamic>>(
|
||||
'/users/$userId/user-devices',
|
||||
);
|
||||
final data = response.data!['items'];
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty response from /users/$userId/user-devices');
|
||||
}
|
||||
|
||||
final model = GetAppUsersResponseDto.fromJson(data);
|
||||
return model.toEntity();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -38,20 +29,12 @@ class UsersRemoteDatasourceImpl implements UsersRemoteDatasource {
|
||||
required String userId,
|
||||
required UpdateUserRequestEntity request,
|
||||
}) async {
|
||||
try {
|
||||
final body = request.toModel().toJson();
|
||||
await _repository.put<void>('/users/$userId', body: body);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to update user');
|
||||
}
|
||||
final body = request.toDto().toJson();
|
||||
await _repository.put<void>('/users/$userId', body: body);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteUser({required String userId}) async {
|
||||
try {
|
||||
await _repository.delete<void>('/users/$userId');
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to delete device');
|
||||
}
|
||||
await _repository.delete<void>('/users/$userId');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'change_password_request_dto.freezed.dart';
|
||||
part 'change_password_request_dto.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class ChangePasswordRequestDto with _$ChangePasswordRequestDto {
|
||||
const factory ChangePasswordRequestDto({required String password}) =
|
||||
_ChangePasswordRequestDto;
|
||||
|
||||
factory ChangePasswordRequestDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChangePasswordRequestDtoFromJson(json);
|
||||
}
|
||||
|
||||
extension ChangePasswordRequestDtoMapper on ChangePasswordRequestEntity {
|
||||
ChangePasswordRequestDto toDto() =>
|
||||
ChangePasswordRequestDto(password: password);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// 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 'change_password_request_model.dart';
|
||||
part of 'change_password_request_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
@@ -13,22 +13,22 @@ part of 'change_password_request_model.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChangePasswordRequestModel {
|
||||
mixin _$ChangePasswordRequestDto {
|
||||
|
||||
String get password;
|
||||
/// Create a copy of ChangePasswordRequestModel
|
||||
/// Create a copy of ChangePasswordRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ChangePasswordRequestModelCopyWith<ChangePasswordRequestModel> get copyWith => _$ChangePasswordRequestModelCopyWithImpl<ChangePasswordRequestModel>(this as ChangePasswordRequestModel, _$identity);
|
||||
$ChangePasswordRequestDtoCopyWith<ChangePasswordRequestDto> get copyWith => _$ChangePasswordRequestDtoCopyWithImpl<ChangePasswordRequestDto>(this as ChangePasswordRequestDto, _$identity);
|
||||
|
||||
/// Serializes this ChangePasswordRequestModel to a JSON map.
|
||||
/// Serializes this ChangePasswordRequestDto to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChangePasswordRequestModel&&(identical(other.password, password) || other.password == password));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChangePasswordRequestDto&&(identical(other.password, password) || other.password == password));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,password);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChangePasswordRequestModel(password: $password)';
|
||||
return 'ChangePasswordRequestDto(password: $password)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ChangePasswordRequestModelCopyWith<$Res> {
|
||||
factory $ChangePasswordRequestModelCopyWith(ChangePasswordRequestModel value, $Res Function(ChangePasswordRequestModel) _then) = _$ChangePasswordRequestModelCopyWithImpl;
|
||||
abstract mixin class $ChangePasswordRequestDtoCopyWith<$Res> {
|
||||
factory $ChangePasswordRequestDtoCopyWith(ChangePasswordRequestDto value, $Res Function(ChangePasswordRequestDto) _then) = _$ChangePasswordRequestDtoCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String password
|
||||
@@ -56,14 +56,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ChangePasswordRequestModelCopyWithImpl<$Res>
|
||||
implements $ChangePasswordRequestModelCopyWith<$Res> {
|
||||
_$ChangePasswordRequestModelCopyWithImpl(this._self, this._then);
|
||||
class _$ChangePasswordRequestDtoCopyWithImpl<$Res>
|
||||
implements $ChangePasswordRequestDtoCopyWith<$Res> {
|
||||
_$ChangePasswordRequestDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ChangePasswordRequestModel _self;
|
||||
final $Res Function(ChangePasswordRequestModel) _then;
|
||||
final ChangePasswordRequestDto _self;
|
||||
final $Res Function(ChangePasswordRequestDto) _then;
|
||||
|
||||
/// Create a copy of ChangePasswordRequestModel
|
||||
/// Create a copy of ChangePasswordRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? password = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -75,8 +75,8 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ChangePasswordRequestModel].
|
||||
extension ChangePasswordRequestModelPatterns on ChangePasswordRequestModel {
|
||||
/// Adds pattern-matching-related methods to [ChangePasswordRequestDto].
|
||||
extension ChangePasswordRequestDtoPatterns on ChangePasswordRequestDto {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -89,10 +89,10 @@ extension ChangePasswordRequestModelPatterns on ChangePasswordRequestModel {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ChangePasswordRequestModel value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ChangePasswordRequestDto value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel() when $default != null:
|
||||
case _ChangePasswordRequestDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -111,10 +111,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ChangePasswordRequestModel value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ChangePasswordRequestDto value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel():
|
||||
case _ChangePasswordRequestDto():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -132,10 +132,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ChangePasswordRequestModel value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ChangePasswordRequestDto value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel() when $default != null:
|
||||
case _ChangePasswordRequestDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String password)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel() when $default != null:
|
||||
case _ChangePasswordRequestDto() when $default != null:
|
||||
return $default(_that.password);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -176,7 +176,7 @@ return $default(_that.password);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String password) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel():
|
||||
case _ChangePasswordRequestDto():
|
||||
return $default(_that.password);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -196,7 +196,7 @@ return $default(_that.password);case _:
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String password)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordRequestModel() when $default != null:
|
||||
case _ChangePasswordRequestDto() when $default != null:
|
||||
return $default(_that.password);case _:
|
||||
return null;
|
||||
|
||||
@@ -208,26 +208,26 @@ return $default(_that.password);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _ChangePasswordRequestModel implements ChangePasswordRequestModel {
|
||||
const _ChangePasswordRequestModel({required this.password});
|
||||
factory _ChangePasswordRequestModel.fromJson(Map<String, dynamic> json) => _$ChangePasswordRequestModelFromJson(json);
|
||||
class _ChangePasswordRequestDto implements ChangePasswordRequestDto {
|
||||
const _ChangePasswordRequestDto({required this.password});
|
||||
factory _ChangePasswordRequestDto.fromJson(Map<String, dynamic> json) => _$ChangePasswordRequestDtoFromJson(json);
|
||||
|
||||
@override final String password;
|
||||
|
||||
/// Create a copy of ChangePasswordRequestModel
|
||||
/// Create a copy of ChangePasswordRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ChangePasswordRequestModelCopyWith<_ChangePasswordRequestModel> get copyWith => __$ChangePasswordRequestModelCopyWithImpl<_ChangePasswordRequestModel>(this, _$identity);
|
||||
_$ChangePasswordRequestDtoCopyWith<_ChangePasswordRequestDto> get copyWith => __$ChangePasswordRequestDtoCopyWithImpl<_ChangePasswordRequestDto>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$ChangePasswordRequestModelToJson(this, );
|
||||
return _$ChangePasswordRequestDtoToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChangePasswordRequestModel&&(identical(other.password, password) || other.password == password));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChangePasswordRequestDto&&(identical(other.password, password) || other.password == password));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -236,15 +236,15 @@ int get hashCode => Object.hash(runtimeType,password);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChangePasswordRequestModel(password: $password)';
|
||||
return 'ChangePasswordRequestDto(password: $password)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ChangePasswordRequestModelCopyWith<$Res> implements $ChangePasswordRequestModelCopyWith<$Res> {
|
||||
factory _$ChangePasswordRequestModelCopyWith(_ChangePasswordRequestModel value, $Res Function(_ChangePasswordRequestModel) _then) = __$ChangePasswordRequestModelCopyWithImpl;
|
||||
abstract mixin class _$ChangePasswordRequestDtoCopyWith<$Res> implements $ChangePasswordRequestDtoCopyWith<$Res> {
|
||||
factory _$ChangePasswordRequestDtoCopyWith(_ChangePasswordRequestDto value, $Res Function(_ChangePasswordRequestDto) _then) = __$ChangePasswordRequestDtoCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String password
|
||||
@@ -255,17 +255,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ChangePasswordRequestModelCopyWithImpl<$Res>
|
||||
implements _$ChangePasswordRequestModelCopyWith<$Res> {
|
||||
__$ChangePasswordRequestModelCopyWithImpl(this._self, this._then);
|
||||
class __$ChangePasswordRequestDtoCopyWithImpl<$Res>
|
||||
implements _$ChangePasswordRequestDtoCopyWith<$Res> {
|
||||
__$ChangePasswordRequestDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ChangePasswordRequestModel _self;
|
||||
final $Res Function(_ChangePasswordRequestModel) _then;
|
||||
final _ChangePasswordRequestDto _self;
|
||||
final $Res Function(_ChangePasswordRequestDto) _then;
|
||||
|
||||
/// Create a copy of ChangePasswordRequestModel
|
||||
/// Create a copy of ChangePasswordRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? password = null,}) {
|
||||
return _then(_ChangePasswordRequestModel(
|
||||
return _then(_ChangePasswordRequestDto(
|
||||
password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
@@ -1,15 +1,15 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'change_password_request_model.dart';
|
||||
part of 'change_password_request_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_ChangePasswordRequestModel _$ChangePasswordRequestModelFromJson(
|
||||
_ChangePasswordRequestDto _$ChangePasswordRequestDtoFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _ChangePasswordRequestModel(password: json['password'] as String);
|
||||
) => _ChangePasswordRequestDto(password: json['password'] as String);
|
||||
|
||||
Map<String, dynamic> _$ChangePasswordRequestModelToJson(
|
||||
_ChangePasswordRequestModel instance,
|
||||
Map<String, dynamic> _$ChangePasswordRequestDtoToJson(
|
||||
_ChangePasswordRequestDto instance,
|
||||
) => <String, dynamic>{'password': instance.password};
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'change_password_request_model.freezed.dart';
|
||||
part 'change_password_request_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class ChangePasswordRequestModel with _$ChangePasswordRequestModel {
|
||||
const factory ChangePasswordRequestModel({required String password}) =
|
||||
_ChangePasswordRequestModel;
|
||||
|
||||
factory ChangePasswordRequestModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChangePasswordRequestModelFromJson(json);
|
||||
}
|
||||
|
||||
extension ChangePasswordRequestModelMapper on ChangePasswordRequestEntity {
|
||||
ChangePasswordRequestModel toModel() =>
|
||||
ChangePasswordRequestModel(password: password);
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
part 'get_app_users_response_model.freezed.dart';
|
||||
part 'get_app_users_response_model.g.dart';
|
||||
part 'get_app_users_response_dto.freezed.dart';
|
||||
part 'get_app_users_response_dto.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class GetAppUsersResponseModel with _$GetAppUsersResponseModel {
|
||||
const factory GetAppUsersResponseModel({
|
||||
required List<GetAppUsersItemResponseModel> items,
|
||||
}) = _GetAppUsersResponseModel;
|
||||
abstract class GetAppUsersResponseDto with _$GetAppUsersResponseDto {
|
||||
const factory GetAppUsersResponseDto({
|
||||
required List<GetAppUsersItemResponseDto> items,
|
||||
}) = _GetAppUsersResponseDto;
|
||||
|
||||
factory GetAppUsersResponseModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetAppUsersResponseModelFromJson(json);
|
||||
factory GetAppUsersResponseDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetAppUsersResponseDtoFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class GetAppUsersItemResponseModel
|
||||
with _$GetAppUsersItemResponseModel {
|
||||
const factory GetAppUsersItemResponseModel({
|
||||
abstract class GetAppUsersItemResponseDto
|
||||
with _$GetAppUsersItemResponseDto {
|
||||
const factory GetAppUsersItemResponseDto({
|
||||
required String id,
|
||||
required String delegationId,
|
||||
required String email,
|
||||
@@ -32,17 +32,17 @@ abstract class GetAppUsersItemResponseModel
|
||||
required String lastName,
|
||||
required bool hasApiKey,
|
||||
required String phone,
|
||||
}) = _GetAppUsersItemResponseModel;
|
||||
}) = _GetAppUsersItemResponseDto;
|
||||
|
||||
factory GetAppUsersItemResponseModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetAppUsersItemResponseModelFromJson(json);
|
||||
factory GetAppUsersItemResponseDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetAppUsersItemResponseDtoFromJson(json);
|
||||
}
|
||||
|
||||
extension GetUsersResponseModelMapper on GetAppUsersResponseModel {
|
||||
extension GetAppUsersResponseDtoMapper on GetAppUsersResponseDto {
|
||||
List<UserEntity> toEntity() {
|
||||
return items
|
||||
.map(
|
||||
(GetAppUsersItemResponseModel item) => UserEntity(
|
||||
(GetAppUsersItemResponseDto item) => UserEntity(
|
||||
id: item.id,
|
||||
delegationId: item.delegationId,
|
||||
email: item.email,
|
||||
@@ -3,7 +3,7 @@
|
||||
// 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 'get_app_users_response_model.dart';
|
||||
part of 'get_app_users_response_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
@@ -13,22 +13,22 @@ part of 'get_app_users_response_model.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$GetAppUsersResponseModel {
|
||||
mixin _$GetAppUsersResponseDto {
|
||||
|
||||
List<GetAppUsersItemResponseModel> get items;
|
||||
/// Create a copy of GetAppUsersResponseModel
|
||||
List<GetAppUsersItemResponseDto> get items;
|
||||
/// Create a copy of GetAppUsersResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$GetAppUsersResponseModelCopyWith<GetAppUsersResponseModel> get copyWith => _$GetAppUsersResponseModelCopyWithImpl<GetAppUsersResponseModel>(this as GetAppUsersResponseModel, _$identity);
|
||||
$GetAppUsersResponseDtoCopyWith<GetAppUsersResponseDto> get copyWith => _$GetAppUsersResponseDtoCopyWithImpl<GetAppUsersResponseDto>(this as GetAppUsersResponseDto, _$identity);
|
||||
|
||||
/// Serializes this GetAppUsersResponseModel to a JSON map.
|
||||
/// Serializes this GetAppUsersResponseDto to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetAppUsersResponseModel&&const DeepCollectionEquality().equals(other.items, items));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetAppUsersResponseDto&&const DeepCollectionEquality().equals(other.items, items));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,18 +37,18 @@ int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetAppUsersResponseModel(items: $items)';
|
||||
return 'GetAppUsersResponseDto(items: $items)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $GetAppUsersResponseModelCopyWith<$Res> {
|
||||
factory $GetAppUsersResponseModelCopyWith(GetAppUsersResponseModel value, $Res Function(GetAppUsersResponseModel) _then) = _$GetAppUsersResponseModelCopyWithImpl;
|
||||
abstract mixin class $GetAppUsersResponseDtoCopyWith<$Res> {
|
||||
factory $GetAppUsersResponseDtoCopyWith(GetAppUsersResponseDto value, $Res Function(GetAppUsersResponseDto) _then) = _$GetAppUsersResponseDtoCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<GetAppUsersItemResponseModel> items
|
||||
List<GetAppUsersItemResponseDto> items
|
||||
});
|
||||
|
||||
|
||||
@@ -56,27 +56,27 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$GetAppUsersResponseModelCopyWithImpl<$Res>
|
||||
implements $GetAppUsersResponseModelCopyWith<$Res> {
|
||||
_$GetAppUsersResponseModelCopyWithImpl(this._self, this._then);
|
||||
class _$GetAppUsersResponseDtoCopyWithImpl<$Res>
|
||||
implements $GetAppUsersResponseDtoCopyWith<$Res> {
|
||||
_$GetAppUsersResponseDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final GetAppUsersResponseModel _self;
|
||||
final $Res Function(GetAppUsersResponseModel) _then;
|
||||
final GetAppUsersResponseDto _self;
|
||||
final $Res Function(GetAppUsersResponseDto) _then;
|
||||
|
||||
/// Create a copy of GetAppUsersResponseModel
|
||||
/// Create a copy of GetAppUsersResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? items = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
||||
as List<GetAppUsersItemResponseModel>,
|
||||
as List<GetAppUsersItemResponseDto>,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [GetAppUsersResponseModel].
|
||||
extension GetAppUsersResponseModelPatterns on GetAppUsersResponseModel {
|
||||
/// Adds pattern-matching-related methods to [GetAppUsersResponseDto].
|
||||
extension GetAppUsersResponseDtoPatterns on GetAppUsersResponseDto {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -89,10 +89,10 @@ extension GetAppUsersResponseModelPatterns on GetAppUsersResponseModel {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GetAppUsersResponseModel value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GetAppUsersResponseDto value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel() when $default != null:
|
||||
case _GetAppUsersResponseDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -111,10 +111,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GetAppUsersResponseModel value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GetAppUsersResponseDto value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel():
|
||||
case _GetAppUsersResponseDto():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -132,10 +132,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GetAppUsersResponseModel value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GetAppUsersResponseDto value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel() when $default != null:
|
||||
case _GetAppUsersResponseDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -153,9 +153,9 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GetAppUsersItemResponseModel> items)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GetAppUsersItemResponseDto> items)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel() when $default != null:
|
||||
case _GetAppUsersResponseDto() when $default != null:
|
||||
return $default(_that.items);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -174,9 +174,9 @@ return $default(_that.items);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GetAppUsersItemResponseModel> items) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GetAppUsersItemResponseDto> items) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel():
|
||||
case _GetAppUsersResponseDto():
|
||||
return $default(_that.items);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -194,9 +194,9 @@ return $default(_that.items);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GetAppUsersItemResponseModel> items)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GetAppUsersItemResponseDto> items)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersResponseModel() when $default != null:
|
||||
case _GetAppUsersResponseDto() when $default != null:
|
||||
return $default(_that.items);case _:
|
||||
return null;
|
||||
|
||||
@@ -208,32 +208,32 @@ return $default(_that.items);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _GetAppUsersResponseModel implements GetAppUsersResponseModel {
|
||||
const _GetAppUsersResponseModel({required final List<GetAppUsersItemResponseModel> items}): _items = items;
|
||||
factory _GetAppUsersResponseModel.fromJson(Map<String, dynamic> json) => _$GetAppUsersResponseModelFromJson(json);
|
||||
class _GetAppUsersResponseDto implements GetAppUsersResponseDto {
|
||||
const _GetAppUsersResponseDto({required final List<GetAppUsersItemResponseDto> items}): _items = items;
|
||||
factory _GetAppUsersResponseDto.fromJson(Map<String, dynamic> json) => _$GetAppUsersResponseDtoFromJson(json);
|
||||
|
||||
final List<GetAppUsersItemResponseModel> _items;
|
||||
@override List<GetAppUsersItemResponseModel> get items {
|
||||
final List<GetAppUsersItemResponseDto> _items;
|
||||
@override List<GetAppUsersItemResponseDto> get items {
|
||||
if (_items is EqualUnmodifiableListView) return _items;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_items);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of GetAppUsersResponseModel
|
||||
/// Create a copy of GetAppUsersResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$GetAppUsersResponseModelCopyWith<_GetAppUsersResponseModel> get copyWith => __$GetAppUsersResponseModelCopyWithImpl<_GetAppUsersResponseModel>(this, _$identity);
|
||||
_$GetAppUsersResponseDtoCopyWith<_GetAppUsersResponseDto> get copyWith => __$GetAppUsersResponseDtoCopyWithImpl<_GetAppUsersResponseDto>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$GetAppUsersResponseModelToJson(this, );
|
||||
return _$GetAppUsersResponseDtoToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetAppUsersResponseModel&&const DeepCollectionEquality().equals(other._items, _items));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetAppUsersResponseDto&&const DeepCollectionEquality().equals(other._items, _items));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -242,18 +242,18 @@ int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetAppUsersResponseModel(items: $items)';
|
||||
return 'GetAppUsersResponseDto(items: $items)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$GetAppUsersResponseModelCopyWith<$Res> implements $GetAppUsersResponseModelCopyWith<$Res> {
|
||||
factory _$GetAppUsersResponseModelCopyWith(_GetAppUsersResponseModel value, $Res Function(_GetAppUsersResponseModel) _then) = __$GetAppUsersResponseModelCopyWithImpl;
|
||||
abstract mixin class _$GetAppUsersResponseDtoCopyWith<$Res> implements $GetAppUsersResponseDtoCopyWith<$Res> {
|
||||
factory _$GetAppUsersResponseDtoCopyWith(_GetAppUsersResponseDto value, $Res Function(_GetAppUsersResponseDto) _then) = __$GetAppUsersResponseDtoCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<GetAppUsersItemResponseModel> items
|
||||
List<GetAppUsersItemResponseDto> items
|
||||
});
|
||||
|
||||
|
||||
@@ -261,19 +261,19 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$GetAppUsersResponseModelCopyWithImpl<$Res>
|
||||
implements _$GetAppUsersResponseModelCopyWith<$Res> {
|
||||
__$GetAppUsersResponseModelCopyWithImpl(this._self, this._then);
|
||||
class __$GetAppUsersResponseDtoCopyWithImpl<$Res>
|
||||
implements _$GetAppUsersResponseDtoCopyWith<$Res> {
|
||||
__$GetAppUsersResponseDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _GetAppUsersResponseModel _self;
|
||||
final $Res Function(_GetAppUsersResponseModel) _then;
|
||||
final _GetAppUsersResponseDto _self;
|
||||
final $Res Function(_GetAppUsersResponseDto) _then;
|
||||
|
||||
/// Create a copy of GetAppUsersResponseModel
|
||||
/// Create a copy of GetAppUsersResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? items = null,}) {
|
||||
return _then(_GetAppUsersResponseModel(
|
||||
return _then(_GetAppUsersResponseDto(
|
||||
items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||
as List<GetAppUsersItemResponseModel>,
|
||||
as List<GetAppUsersItemResponseDto>,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -282,22 +282,22 @@ as List<GetAppUsersItemResponseModel>,
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$GetAppUsersItemResponseModel {
|
||||
mixin _$GetAppUsersItemResponseDto {
|
||||
|
||||
String get id; String get delegationId; String get email; int get createdAt; int? get updatedAt; String get status; String get role; int get lastLogin; int get currentLogin; String get language; String get firstName; String get lastName; bool get hasApiKey; String get phone;
|
||||
/// Create a copy of GetAppUsersItemResponseModel
|
||||
/// Create a copy of GetAppUsersItemResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$GetAppUsersItemResponseModelCopyWith<GetAppUsersItemResponseModel> get copyWith => _$GetAppUsersItemResponseModelCopyWithImpl<GetAppUsersItemResponseModel>(this as GetAppUsersItemResponseModel, _$identity);
|
||||
$GetAppUsersItemResponseDtoCopyWith<GetAppUsersItemResponseDto> get copyWith => _$GetAppUsersItemResponseDtoCopyWithImpl<GetAppUsersItemResponseDto>(this as GetAppUsersItemResponseDto, _$identity);
|
||||
|
||||
/// Serializes this GetAppUsersItemResponseModel to a JSON map.
|
||||
/// Serializes this GetAppUsersItemResponseDto to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetAppUsersItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetAppUsersItemResponseDto&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -306,15 +306,15 @@ int get hashCode => Object.hash(runtimeType,id,delegationId,email,createdAt,upda
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetAppUsersItemResponseModel(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
return 'GetAppUsersItemResponseDto(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $GetAppUsersItemResponseModelCopyWith<$Res> {
|
||||
factory $GetAppUsersItemResponseModelCopyWith(GetAppUsersItemResponseModel value, $Res Function(GetAppUsersItemResponseModel) _then) = _$GetAppUsersItemResponseModelCopyWithImpl;
|
||||
abstract mixin class $GetAppUsersItemResponseDtoCopyWith<$Res> {
|
||||
factory $GetAppUsersItemResponseDtoCopyWith(GetAppUsersItemResponseDto value, $Res Function(GetAppUsersItemResponseDto) _then) = _$GetAppUsersItemResponseDtoCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String delegationId, String email, int createdAt, int? updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone
|
||||
@@ -325,14 +325,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$GetAppUsersItemResponseModelCopyWithImpl<$Res>
|
||||
implements $GetAppUsersItemResponseModelCopyWith<$Res> {
|
||||
_$GetAppUsersItemResponseModelCopyWithImpl(this._self, this._then);
|
||||
class _$GetAppUsersItemResponseDtoCopyWithImpl<$Res>
|
||||
implements $GetAppUsersItemResponseDtoCopyWith<$Res> {
|
||||
_$GetAppUsersItemResponseDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final GetAppUsersItemResponseModel _self;
|
||||
final $Res Function(GetAppUsersItemResponseModel) _then;
|
||||
final GetAppUsersItemResponseDto _self;
|
||||
final $Res Function(GetAppUsersItemResponseDto) _then;
|
||||
|
||||
/// Create a copy of GetAppUsersItemResponseModel
|
||||
/// Create a copy of GetAppUsersItemResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delegationId = null,Object? email = null,Object? createdAt = null,Object? updatedAt = freezed,Object? status = null,Object? role = null,Object? lastLogin = null,Object? currentLogin = null,Object? language = null,Object? firstName = null,Object? lastName = null,Object? hasApiKey = null,Object? phone = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -357,8 +357,8 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [GetAppUsersItemResponseModel].
|
||||
extension GetAppUsersItemResponseModelPatterns on GetAppUsersItemResponseModel {
|
||||
/// Adds pattern-matching-related methods to [GetAppUsersItemResponseDto].
|
||||
extension GetAppUsersItemResponseDtoPatterns on GetAppUsersItemResponseDto {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -371,10 +371,10 @@ extension GetAppUsersItemResponseModelPatterns on GetAppUsersItemResponseModel {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GetAppUsersItemResponseModel value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _GetAppUsersItemResponseDto value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel() when $default != null:
|
||||
case _GetAppUsersItemResponseDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -393,10 +393,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GetAppUsersItemResponseModel value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _GetAppUsersItemResponseDto value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel():
|
||||
case _GetAppUsersItemResponseDto():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -414,10 +414,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GetAppUsersItemResponseModel value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _GetAppUsersItemResponseDto value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel() when $default != null:
|
||||
case _GetAppUsersItemResponseDto() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -437,7 +437,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String delegationId, String email, int createdAt, int? updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel() when $default != null:
|
||||
case _GetAppUsersItemResponseDto() when $default != null:
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -458,7 +458,7 @@ return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.up
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String delegationId, String email, int createdAt, int? updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel():
|
||||
case _GetAppUsersItemResponseDto():
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
@@ -478,7 +478,7 @@ return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.up
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String delegationId, String email, int createdAt, int? updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _GetAppUsersItemResponseModel() when $default != null:
|
||||
case _GetAppUsersItemResponseDto() when $default != null:
|
||||
return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.updatedAt,_that.status,_that.role,_that.lastLogin,_that.currentLogin,_that.language,_that.firstName,_that.lastName,_that.hasApiKey,_that.phone);case _:
|
||||
return null;
|
||||
|
||||
@@ -490,9 +490,9 @@ return $default(_that.id,_that.delegationId,_that.email,_that.createdAt,_that.up
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _GetAppUsersItemResponseModel implements GetAppUsersItemResponseModel {
|
||||
const _GetAppUsersItemResponseModel({required this.id, required this.delegationId, required this.email, required this.createdAt, required this.updatedAt, required this.status, required this.role, required this.lastLogin, required this.currentLogin, required this.language, required this.firstName, required this.lastName, required this.hasApiKey, required this.phone});
|
||||
factory _GetAppUsersItemResponseModel.fromJson(Map<String, dynamic> json) => _$GetAppUsersItemResponseModelFromJson(json);
|
||||
class _GetAppUsersItemResponseDto implements GetAppUsersItemResponseDto {
|
||||
const _GetAppUsersItemResponseDto({required this.id, required this.delegationId, required this.email, required this.createdAt, required this.updatedAt, required this.status, required this.role, required this.lastLogin, required this.currentLogin, required this.language, required this.firstName, required this.lastName, required this.hasApiKey, required this.phone});
|
||||
factory _GetAppUsersItemResponseDto.fromJson(Map<String, dynamic> json) => _$GetAppUsersItemResponseDtoFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String delegationId;
|
||||
@@ -509,20 +509,20 @@ class _GetAppUsersItemResponseModel implements GetAppUsersItemResponseModel {
|
||||
@override final bool hasApiKey;
|
||||
@override final String phone;
|
||||
|
||||
/// Create a copy of GetAppUsersItemResponseModel
|
||||
/// Create a copy of GetAppUsersItemResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$GetAppUsersItemResponseModelCopyWith<_GetAppUsersItemResponseModel> get copyWith => __$GetAppUsersItemResponseModelCopyWithImpl<_GetAppUsersItemResponseModel>(this, _$identity);
|
||||
_$GetAppUsersItemResponseDtoCopyWith<_GetAppUsersItemResponseDto> get copyWith => __$GetAppUsersItemResponseDtoCopyWithImpl<_GetAppUsersItemResponseDto>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$GetAppUsersItemResponseModelToJson(this, );
|
||||
return _$GetAppUsersItemResponseDtoToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetAppUsersItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetAppUsersItemResponseDto&&(identical(other.id, id) || other.id == id)&&(identical(other.delegationId, delegationId) || other.delegationId == delegationId)&&(identical(other.email, email) || other.email == email)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.status, status) || other.status == status)&&(identical(other.role, role) || other.role == role)&&(identical(other.lastLogin, lastLogin) || other.lastLogin == lastLogin)&&(identical(other.currentLogin, currentLogin) || other.currentLogin == currentLogin)&&(identical(other.language, language) || other.language == language)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.hasApiKey, hasApiKey) || other.hasApiKey == hasApiKey)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -531,15 +531,15 @@ int get hashCode => Object.hash(runtimeType,id,delegationId,email,createdAt,upda
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GetAppUsersItemResponseModel(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
return 'GetAppUsersItemResponseDto(id: $id, delegationId: $delegationId, email: $email, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, role: $role, lastLogin: $lastLogin, currentLogin: $currentLogin, language: $language, firstName: $firstName, lastName: $lastName, hasApiKey: $hasApiKey, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$GetAppUsersItemResponseModelCopyWith<$Res> implements $GetAppUsersItemResponseModelCopyWith<$Res> {
|
||||
factory _$GetAppUsersItemResponseModelCopyWith(_GetAppUsersItemResponseModel value, $Res Function(_GetAppUsersItemResponseModel) _then) = __$GetAppUsersItemResponseModelCopyWithImpl;
|
||||
abstract mixin class _$GetAppUsersItemResponseDtoCopyWith<$Res> implements $GetAppUsersItemResponseDtoCopyWith<$Res> {
|
||||
factory _$GetAppUsersItemResponseDtoCopyWith(_GetAppUsersItemResponseDto value, $Res Function(_GetAppUsersItemResponseDto) _then) = __$GetAppUsersItemResponseDtoCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String delegationId, String email, int createdAt, int? updatedAt, String status, String role, int lastLogin, int currentLogin, String language, String firstName, String lastName, bool hasApiKey, String phone
|
||||
@@ -550,17 +550,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$GetAppUsersItemResponseModelCopyWithImpl<$Res>
|
||||
implements _$GetAppUsersItemResponseModelCopyWith<$Res> {
|
||||
__$GetAppUsersItemResponseModelCopyWithImpl(this._self, this._then);
|
||||
class __$GetAppUsersItemResponseDtoCopyWithImpl<$Res>
|
||||
implements _$GetAppUsersItemResponseDtoCopyWith<$Res> {
|
||||
__$GetAppUsersItemResponseDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _GetAppUsersItemResponseModel _self;
|
||||
final $Res Function(_GetAppUsersItemResponseModel) _then;
|
||||
final _GetAppUsersItemResponseDto _self;
|
||||
final $Res Function(_GetAppUsersItemResponseDto) _then;
|
||||
|
||||
/// Create a copy of GetAppUsersItemResponseModel
|
||||
/// Create a copy of GetAppUsersItemResponseDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delegationId = null,Object? email = null,Object? createdAt = null,Object? updatedAt = freezed,Object? status = null,Object? role = null,Object? lastLogin = null,Object? currentLogin = null,Object? language = null,Object? firstName = null,Object? lastName = null,Object? hasApiKey = null,Object? phone = null,}) {
|
||||
return _then(_GetAppUsersItemResponseModel(
|
||||
return _then(_GetAppUsersItemResponseDto(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,delegationId: null == delegationId ? _self.delegationId : delegationId // ignore: cast_nullable_to_non_nullable
|
||||
as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
|
||||
@@ -1,28 +1,28 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'get_app_users_response_model.dart';
|
||||
part of 'get_app_users_response_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_GetAppUsersResponseModel _$GetAppUsersResponseModelFromJson(
|
||||
_GetAppUsersResponseDto _$GetAppUsersResponseDtoFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _GetAppUsersResponseModel(
|
||||
) => _GetAppUsersResponseDto(
|
||||
items: (json['items'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetAppUsersItemResponseModel.fromJson(e as Map<String, dynamic>),
|
||||
(e) => GetAppUsersItemResponseDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GetAppUsersResponseModelToJson(
|
||||
_GetAppUsersResponseModel instance,
|
||||
Map<String, dynamic> _$GetAppUsersResponseDtoToJson(
|
||||
_GetAppUsersResponseDto instance,
|
||||
) => <String, dynamic>{'items': instance.items};
|
||||
|
||||
_GetAppUsersItemResponseModel _$GetAppUsersItemResponseModelFromJson(
|
||||
_GetAppUsersItemResponseDto _$GetAppUsersItemResponseDtoFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _GetAppUsersItemResponseModel(
|
||||
) => _GetAppUsersItemResponseDto(
|
||||
id: json['id'] as String,
|
||||
delegationId: json['delegationId'] as String,
|
||||
email: json['email'] as String,
|
||||
@@ -39,8 +39,8 @@ _GetAppUsersItemResponseModel _$GetAppUsersItemResponseModelFromJson(
|
||||
phone: json['phone'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GetAppUsersItemResponseModelToJson(
|
||||
_GetAppUsersItemResponseModel instance,
|
||||
Map<String, dynamic> _$GetAppUsersItemResponseDtoToJson(
|
||||
_GetAppUsersItemResponseDto instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'delegationId': instance.delegationId,
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'package:account/src/features/linked_devices/domain/entities/update_device_request_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'update_device_request_model.freezed.dart';
|
||||
part 'update_device_request_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class UpdateDeviceRequestModel with _$UpdateDeviceRequestModel {
|
||||
const factory UpdateDeviceRequestModel({
|
||||
required String identificator,
|
||||
required String carrierName,
|
||||
}) = _UpdateDeviceRequestModel;
|
||||
|
||||
factory UpdateDeviceRequestModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateDeviceRequestModelFromJson(json);
|
||||
}
|
||||
|
||||
extension UpdateDeviceRequestModelMapper on UpdateDeviceRequestEntity {
|
||||
UpdateDeviceRequestModel toModel() => UpdateDeviceRequestModel(
|
||||
identificator: identificator,
|
||||
carrierName: carrierName,
|
||||
);
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
// 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 'update_device_request_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdateDeviceRequestModel {
|
||||
|
||||
String get identificator; String get carrierName;
|
||||
/// Create a copy of UpdateDeviceRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$UpdateDeviceRequestModelCopyWith<UpdateDeviceRequestModel> get copyWith => _$UpdateDeviceRequestModelCopyWithImpl<UpdateDeviceRequestModel>(this as UpdateDeviceRequestModel, _$identity);
|
||||
|
||||
/// Serializes this UpdateDeviceRequestModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateDeviceRequestModel&&(identical(other.identificator, identificator) || other.identificator == identificator)&&(identical(other.carrierName, carrierName) || other.carrierName == carrierName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,identificator,carrierName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateDeviceRequestModel(identificator: $identificator, carrierName: $carrierName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $UpdateDeviceRequestModelCopyWith<$Res> {
|
||||
factory $UpdateDeviceRequestModelCopyWith(UpdateDeviceRequestModel value, $Res Function(UpdateDeviceRequestModel) _then) = _$UpdateDeviceRequestModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String identificator, String carrierName
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$UpdateDeviceRequestModelCopyWithImpl<$Res>
|
||||
implements $UpdateDeviceRequestModelCopyWith<$Res> {
|
||||
_$UpdateDeviceRequestModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final UpdateDeviceRequestModel _self;
|
||||
final $Res Function(UpdateDeviceRequestModel) _then;
|
||||
|
||||
/// Create a copy of UpdateDeviceRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? identificator = null,Object? carrierName = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
identificator: null == identificator ? _self.identificator : identificator // ignore: cast_nullable_to_non_nullable
|
||||
as String,carrierName: null == carrierName ? _self.carrierName : carrierName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [UpdateDeviceRequestModel].
|
||||
extension UpdateDeviceRequestModelPatterns on UpdateDeviceRequestModel {
|
||||
/// 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( _UpdateDeviceRequestModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel() 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( _UpdateDeviceRequestModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel():
|
||||
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( _UpdateDeviceRequestModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel() 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 identificator, String carrierName)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel() when $default != null:
|
||||
return $default(_that.identificator,_that.carrierName);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 identificator, String carrierName) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel():
|
||||
return $default(_that.identificator,_that.carrierName);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 identificator, String carrierName)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateDeviceRequestModel() when $default != null:
|
||||
return $default(_that.identificator,_that.carrierName);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _UpdateDeviceRequestModel implements UpdateDeviceRequestModel {
|
||||
const _UpdateDeviceRequestModel({required this.identificator, required this.carrierName});
|
||||
factory _UpdateDeviceRequestModel.fromJson(Map<String, dynamic> json) => _$UpdateDeviceRequestModelFromJson(json);
|
||||
|
||||
@override final String identificator;
|
||||
@override final String carrierName;
|
||||
|
||||
/// Create a copy of UpdateDeviceRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$UpdateDeviceRequestModelCopyWith<_UpdateDeviceRequestModel> get copyWith => __$UpdateDeviceRequestModelCopyWithImpl<_UpdateDeviceRequestModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$UpdateDeviceRequestModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateDeviceRequestModel&&(identical(other.identificator, identificator) || other.identificator == identificator)&&(identical(other.carrierName, carrierName) || other.carrierName == carrierName));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,identificator,carrierName);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateDeviceRequestModel(identificator: $identificator, carrierName: $carrierName)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$UpdateDeviceRequestModelCopyWith<$Res> implements $UpdateDeviceRequestModelCopyWith<$Res> {
|
||||
factory _$UpdateDeviceRequestModelCopyWith(_UpdateDeviceRequestModel value, $Res Function(_UpdateDeviceRequestModel) _then) = __$UpdateDeviceRequestModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String identificator, String carrierName
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$UpdateDeviceRequestModelCopyWithImpl<$Res>
|
||||
implements _$UpdateDeviceRequestModelCopyWith<$Res> {
|
||||
__$UpdateDeviceRequestModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _UpdateDeviceRequestModel _self;
|
||||
final $Res Function(_UpdateDeviceRequestModel) _then;
|
||||
|
||||
/// Create a copy of UpdateDeviceRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? identificator = null,Object? carrierName = null,}) {
|
||||
return _then(_UpdateDeviceRequestModel(
|
||||
identificator: null == identificator ? _self.identificator : identificator // ignore: cast_nullable_to_non_nullable
|
||||
as String,carrierName: null == carrierName ? _self.carrierName : carrierName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,21 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'update_device_request_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_UpdateDeviceRequestModel _$UpdateDeviceRequestModelFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _UpdateDeviceRequestModel(
|
||||
identificator: json['identificator'] as String,
|
||||
carrierName: json['carrierName'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UpdateDeviceRequestModelToJson(
|
||||
_UpdateDeviceRequestModel instance,
|
||||
) => <String, dynamic>{
|
||||
'identificator': instance.identificator,
|
||||
'carrierName': instance.carrierName,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:account/src/features/personal_data/domain/entities/update_user_request_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'update_user_request_dto.freezed.dart';
|
||||
part 'update_user_request_dto.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class UpdateUserRequestDto with _$UpdateUserRequestDto {
|
||||
const factory UpdateUserRequestDto({
|
||||
required String id,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required String phone,
|
||||
required String language,
|
||||
}) = _UpdateUserRequestDto;
|
||||
|
||||
factory UpdateUserRequestDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateUserRequestDtoFromJson(json);
|
||||
}
|
||||
|
||||
extension UpdateUserRequestDtoMapper on UpdateUserRequestEntity {
|
||||
UpdateUserRequestDto toDto() => UpdateUserRequestDto(
|
||||
id: id,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
phone: phone,
|
||||
language: language,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// 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 'update_user_request_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdateUserRequestDto {
|
||||
|
||||
String get id; String get firstName; String get lastName; String get phone; String get language;
|
||||
/// Create a copy of UpdateUserRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$UpdateUserRequestDtoCopyWith<UpdateUserRequestDto> get copyWith => _$UpdateUserRequestDtoCopyWithImpl<UpdateUserRequestDto>(this as UpdateUserRequestDto, _$identity);
|
||||
|
||||
/// Serializes this UpdateUserRequestDto to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateUserRequestDto&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.language, language) || other.language == language));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,firstName,lastName,phone,language);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateUserRequestDto(id: $id, firstName: $firstName, lastName: $lastName, phone: $phone, language: $language)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $UpdateUserRequestDtoCopyWith<$Res> {
|
||||
factory $UpdateUserRequestDtoCopyWith(UpdateUserRequestDto value, $Res Function(UpdateUserRequestDto) _then) = _$UpdateUserRequestDtoCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String firstName, String lastName, String phone, String language
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$UpdateUserRequestDtoCopyWithImpl<$Res>
|
||||
implements $UpdateUserRequestDtoCopyWith<$Res> {
|
||||
_$UpdateUserRequestDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final UpdateUserRequestDto _self;
|
||||
final $Res Function(UpdateUserRequestDto) _then;
|
||||
|
||||
/// Create a copy of UpdateUserRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? lastName = null,Object? phone = null,Object? language = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [UpdateUserRequestDto].
|
||||
extension UpdateUserRequestDtoPatterns on UpdateUserRequestDto {
|
||||
/// 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( _UpdateUserRequestDto value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto() 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( _UpdateUserRequestDto value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto():
|
||||
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( _UpdateUserRequestDto value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto() 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 id, String firstName, String lastName, String phone, String language)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.lastName,_that.phone,_that.language);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 id, String firstName, String lastName, String phone, String language) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto():
|
||||
return $default(_that.id,_that.firstName,_that.lastName,_that.phone,_that.language);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 id, String firstName, String lastName, String phone, String language)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestDto() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.lastName,_that.phone,_that.language);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _UpdateUserRequestDto implements UpdateUserRequestDto {
|
||||
const _UpdateUserRequestDto({required this.id, required this.firstName, required this.lastName, required this.phone, required this.language});
|
||||
factory _UpdateUserRequestDto.fromJson(Map<String, dynamic> json) => _$UpdateUserRequestDtoFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String firstName;
|
||||
@override final String lastName;
|
||||
@override final String phone;
|
||||
@override final String language;
|
||||
|
||||
/// Create a copy of UpdateUserRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$UpdateUserRequestDtoCopyWith<_UpdateUserRequestDto> get copyWith => __$UpdateUserRequestDtoCopyWithImpl<_UpdateUserRequestDto>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$UpdateUserRequestDtoToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateUserRequestDto&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.phone, phone) || other.phone == phone)&&(identical(other.language, language) || other.language == language));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,firstName,lastName,phone,language);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateUserRequestDto(id: $id, firstName: $firstName, lastName: $lastName, phone: $phone, language: $language)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$UpdateUserRequestDtoCopyWith<$Res> implements $UpdateUserRequestDtoCopyWith<$Res> {
|
||||
factory _$UpdateUserRequestDtoCopyWith(_UpdateUserRequestDto value, $Res Function(_UpdateUserRequestDto) _then) = __$UpdateUserRequestDtoCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String firstName, String lastName, String phone, String language
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$UpdateUserRequestDtoCopyWithImpl<$Res>
|
||||
implements _$UpdateUserRequestDtoCopyWith<$Res> {
|
||||
__$UpdateUserRequestDtoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _UpdateUserRequestDto _self;
|
||||
final $Res Function(_UpdateUserRequestDto) _then;
|
||||
|
||||
/// Create a copy of UpdateUserRequestDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? lastName = null,Object? phone = null,Object? language = null,}) {
|
||||
return _then(_UpdateUserRequestDto(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lastName: null == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'update_user_request_dto.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_UpdateUserRequestDto _$UpdateUserRequestDtoFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _UpdateUserRequestDto(
|
||||
id: json['id'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
phone: json['phone'] as String,
|
||||
language: json['language'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UpdateUserRequestDtoToJson(
|
||||
_UpdateUserRequestDto instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'firstName': instance.firstName,
|
||||
'lastName': instance.lastName,
|
||||
'phone': instance.phone,
|
||||
'language': instance.language,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'package:account/src/features/personal_data/domain/entities/update_user_request_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'update_user_request_model.freezed.dart';
|
||||
part 'update_user_request_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class UpdateUserRequestModel with _$UpdateUserRequestModel {
|
||||
const factory UpdateUserRequestModel({
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? phone,
|
||||
}) = _UpdateUserRequestModel;
|
||||
|
||||
factory UpdateUserRequestModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateUserRequestModelFromJson(json);
|
||||
}
|
||||
|
||||
extension UpdateUserRequestModelMapper on UpdateUserRequestEntity {
|
||||
UpdateUserRequestModel toModel() => UpdateUserRequestModel(
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
phone: phone,
|
||||
);
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
// 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 'update_user_request_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdateUserRequestModel {
|
||||
|
||||
String? get firstName; String? get lastName; String? get phone;
|
||||
/// Create a copy of UpdateUserRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$UpdateUserRequestModelCopyWith<UpdateUserRequestModel> get copyWith => _$UpdateUserRequestModelCopyWithImpl<UpdateUserRequestModel>(this as UpdateUserRequestModel, _$identity);
|
||||
|
||||
/// Serializes this UpdateUserRequestModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UpdateUserRequestModel&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,firstName,lastName,phone);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateUserRequestModel(firstName: $firstName, lastName: $lastName, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $UpdateUserRequestModelCopyWith<$Res> {
|
||||
factory $UpdateUserRequestModelCopyWith(UpdateUserRequestModel value, $Res Function(UpdateUserRequestModel) _then) = _$UpdateUserRequestModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String? firstName, String? lastName, String? phone
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$UpdateUserRequestModelCopyWithImpl<$Res>
|
||||
implements $UpdateUserRequestModelCopyWith<$Res> {
|
||||
_$UpdateUserRequestModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final UpdateUserRequestModel _self;
|
||||
final $Res Function(UpdateUserRequestModel) _then;
|
||||
|
||||
/// Create a copy of UpdateUserRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? firstName = freezed,Object? lastName = freezed,Object? phone = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,phone: freezed == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [UpdateUserRequestModel].
|
||||
extension UpdateUserRequestModelPatterns on UpdateUserRequestModel {
|
||||
/// 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( _UpdateUserRequestModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel() 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( _UpdateUserRequestModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel():
|
||||
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( _UpdateUserRequestModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel() 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? firstName, String? lastName, String? phone)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel() when $default != null:
|
||||
return $default(_that.firstName,_that.lastName,_that.phone);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? firstName, String? lastName, String? phone) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel():
|
||||
return $default(_that.firstName,_that.lastName,_that.phone);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? firstName, String? lastName, String? phone)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UpdateUserRequestModel() when $default != null:
|
||||
return $default(_that.firstName,_that.lastName,_that.phone);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _UpdateUserRequestModel implements UpdateUserRequestModel {
|
||||
const _UpdateUserRequestModel({this.firstName, this.lastName, this.phone});
|
||||
factory _UpdateUserRequestModel.fromJson(Map<String, dynamic> json) => _$UpdateUserRequestModelFromJson(json);
|
||||
|
||||
@override final String? firstName;
|
||||
@override final String? lastName;
|
||||
@override final String? phone;
|
||||
|
||||
/// Create a copy of UpdateUserRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$UpdateUserRequestModelCopyWith<_UpdateUserRequestModel> get copyWith => __$UpdateUserRequestModelCopyWithImpl<_UpdateUserRequestModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$UpdateUserRequestModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UpdateUserRequestModel&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.phone, phone) || other.phone == phone));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,firstName,lastName,phone);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateUserRequestModel(firstName: $firstName, lastName: $lastName, phone: $phone)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$UpdateUserRequestModelCopyWith<$Res> implements $UpdateUserRequestModelCopyWith<$Res> {
|
||||
factory _$UpdateUserRequestModelCopyWith(_UpdateUserRequestModel value, $Res Function(_UpdateUserRequestModel) _then) = __$UpdateUserRequestModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String? firstName, String? lastName, String? phone
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$UpdateUserRequestModelCopyWithImpl<$Res>
|
||||
implements _$UpdateUserRequestModelCopyWith<$Res> {
|
||||
__$UpdateUserRequestModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _UpdateUserRequestModel _self;
|
||||
final $Res Function(_UpdateUserRequestModel) _then;
|
||||
|
||||
/// Create a copy of UpdateUserRequestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? firstName = freezed,Object? lastName = freezed,Object? phone = freezed,}) {
|
||||
return _then(_UpdateUserRequestModel(
|
||||
firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,phone: freezed == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,23 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'update_user_request_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_UpdateUserRequestModel _$UpdateUserRequestModelFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _UpdateUserRequestModel(
|
||||
firstName: json['firstName'] as String?,
|
||||
lastName: json['lastName'] as String?,
|
||||
phone: json['phone'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UpdateUserRequestModelToJson(
|
||||
_UpdateUserRequestModel instance,
|
||||
) => <String, dynamic>{
|
||||
'firstName': instance.firstName,
|
||||
'lastName': instance.lastName,
|
||||
'phone': instance.phone,
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
import '../../domain/repositories/change_password_repository.dart';
|
||||
import '../datasource/change_password_remote_datasource.dart';
|
||||
@@ -12,7 +14,11 @@ class ChangePasswordRepositoryImpl implements ChangePasswordRepository {
|
||||
Future<void> changePassword({
|
||||
required String userId,
|
||||
required ChangePasswordRequestEntity request,
|
||||
}) {
|
||||
return _remote.changePassword(userId: userId, request: request);
|
||||
}) async {
|
||||
try {
|
||||
await _remote.changePassword(userId: userId, request: request);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to change password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:account/src/core/data/datasource/devices_remote_datasource.dart';
|
||||
import 'package:account/src/features/linked_devices/domain/entities/update_device_request_entity.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/sf_shared.dart' show DeviceEntity;
|
||||
|
||||
import '../../domain/repositories/devices_repository.dart';
|
||||
|
||||
@@ -9,12 +11,20 @@ class DevicesRepositoryImpl implements DevicesRepository {
|
||||
final DevicesRemoteDatasource _remote;
|
||||
|
||||
@override
|
||||
Future<void> deleteDevice({required String deviceId}) {
|
||||
return _remote.deleteDevice(deviceId: deviceId);
|
||||
Future<void> deleteDevice({required String deviceId}) async {
|
||||
try {
|
||||
await _remote.deleteDevice(deviceId: deviceId);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to delete device');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateDevice({required UpdateDeviceRequestEntity request}) {
|
||||
return _remote.updateDevice(request: request);
|
||||
Future<void> updateDevice({required DeviceEntity device}) async {
|
||||
try {
|
||||
await _remote.updateDevice(device: device);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to update device');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:account/src/features/personal_data/domain/entities/update_user_request_entity.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
import '../../domain/repositories/users_repository.dart';
|
||||
@@ -13,17 +15,32 @@ class UsersRepositoryImpl implements UsersRepository {
|
||||
Future<void> updateUser({
|
||||
required String userId,
|
||||
required UpdateUserRequestEntity request,
|
||||
}) {
|
||||
return _remote.updateUser(userId: userId, request: request);
|
||||
}) async {
|
||||
try {
|
||||
await _remote.updateUser(userId: userId, request: request);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to update user');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserEntity>> getUsers({required String userId}) {
|
||||
return _remote.getUsers(userId: userId);
|
||||
Future<List<UserEntity>> getUsers({required String userId}) async {
|
||||
try {
|
||||
return await _remote.getUsers(userId: userId);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(
|
||||
error,
|
||||
defaultMessage: error.message ?? 'Error getting devices',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteUser({required String userId}) {
|
||||
return _remote.deleteUser(userId: userId);
|
||||
Future<void> deleteUser({required String userId}) async {
|
||||
try {
|
||||
await _remote.deleteUser(userId: userId);
|
||||
} on DioException catch (error) {
|
||||
throw mapDioError(error, defaultMessage: 'Error to delete device');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:account/src/features/linked_devices/domain/entities/update_device_request_entity.dart';
|
||||
import 'package:sf_shared/sf_shared.dart' show DeviceEntity;
|
||||
|
||||
abstract class DevicesRepository {
|
||||
Future<void> deleteDevice({required String deviceId});
|
||||
|
||||
Future<void> updateDevice({required UpdateDeviceRequestEntity request});
|
||||
Future<void> updateDevice({required DeviceEntity device});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'package:account/src/features/account_settings/presentation/state/account_settings_view_model.dart';
|
||||
import 'package:account/src/features/account_settings/presentation/providers/account_settings_controller.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:legacy_ui/legacy_ui.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
@@ -12,37 +15,33 @@ import 'widgets/reg_code_dialog.dart';
|
||||
|
||||
class AccountSettingsScreen extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
static final _privacyUrl =
|
||||
'https://savefamilygps.com/pages/politica-de-privacidad-reloj-gps-infantil-localizador-savefamily';
|
||||
|
||||
const AccountSettingsScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final color = theme.getColorFor(ThemeCode.legacyPrimary);
|
||||
final color = context.sfColors.legacyPrimary;
|
||||
final selectedDevice = ref.watch(selectedDeviceProvider).value;
|
||||
final isLoggingOut = ref.watch(
|
||||
accountSettingsViewModelProvider.select((s) => s.isLoggingOut),
|
||||
accountSettingsControllerProvider.select((s) => s.isLoading),
|
||||
);
|
||||
|
||||
ref.listen(accountSettingsViewModelProvider.select((s) => s.isLoggingOut), (
|
||||
prev,
|
||||
isLoggingOut,
|
||||
) {
|
||||
if (prev == true && !isLoggingOut) {
|
||||
navigationContract.goTo(AppRoutes.legacyLogin);
|
||||
ref.listen(accountSettingsControllerProvider, (prev, next) async {
|
||||
if (prev != null && prev.isLoading && !next.isLoading) {
|
||||
await clearSessionData();
|
||||
ref.invalidate(legacyDevicesProvider);
|
||||
ref.invalidate(selectedDeviceProvider);
|
||||
if (context.mounted) navigationContract.goTo(AppRoutes.legacyLogin);
|
||||
}
|
||||
});
|
||||
|
||||
return LegacyPageLayout(
|
||||
theme: theme,
|
||||
title: context.translate(I18n.accountSettings),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
small: const EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: const EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -78,11 +77,18 @@ class AccountSettingsScreen extends ConsumerWidget {
|
||||
text: I18n.linkedDevices,
|
||||
color: color,
|
||||
),
|
||||
// _item(context, onPressed: () => navigationContract.pushTo(AppRoutes.appUsers), icon: Icons.groups_outlined, text: I18n.appUsers, color: color),
|
||||
_item(
|
||||
context,
|
||||
onPressed: () =>
|
||||
navigationContract.pushTo(AppRoutes.appUsers),
|
||||
icon: Icons.groups_outlined,
|
||||
text: I18n.appUsers,
|
||||
color: color,
|
||||
),
|
||||
_item(
|
||||
context,
|
||||
onPressed: () async {
|
||||
final Uri url = Uri.parse(_privacyUrl);
|
||||
final Uri url = Uri.parse(BrandLinks.privacyPolicy);
|
||||
if (!await launchUrl(url)) {
|
||||
throw Exception('Could not launch $url');
|
||||
}
|
||||
@@ -94,9 +100,9 @@ class AccountSettingsScreen extends ConsumerWidget {
|
||||
_item(
|
||||
context,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
showLegacyDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
builder: (_) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: RegCodeDialog(
|
||||
regCode: selectedDevice?.id ?? '',
|
||||
@@ -124,12 +130,12 @@ class AccountSettingsScreen extends ConsumerWidget {
|
||||
),
|
||||
footer: Container(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(vertical: 12, horizontal: 30),
|
||||
big: EdgeInsets.symmetric(vertical: 10, horizontal: 28),
|
||||
small: const EdgeInsets.symmetric(vertical: 12, horizontal: 30),
|
||||
big: const EdgeInsets.symmetric(vertical: 10, horizontal: 28),
|
||||
),
|
||||
child: PrimaryButton(
|
||||
text: context.translate(I18n.logOut),
|
||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
leading: isLoggingOut
|
||||
? const SizedBox(
|
||||
height: 18,
|
||||
@@ -142,7 +148,7 @@ class AccountSettingsScreen extends ConsumerWidget {
|
||||
: null,
|
||||
onPressed: isLoggingOut
|
||||
? () {}
|
||||
: ref.read(accountSettingsViewModelProvider.notifier).logout,
|
||||
: ref.read(accountSettingsControllerProvider.notifier).logout,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:legacy_auth/legacy_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
part 'account_settings_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class AccountSettingsController extends _$AccountSettingsController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> logout() async {
|
||||
state = const AsyncLoading();
|
||||
try {
|
||||
await ref.read(legacyAuthRepositoryProvider).logout();
|
||||
} catch (_) {}
|
||||
unawaited(ref.read(sfTrackingProvider).legacyAuthLogout());
|
||||
state = const AsyncData(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'account_settings_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AccountSettingsController)
|
||||
const accountSettingsControllerProvider = AccountSettingsControllerProvider._();
|
||||
|
||||
final class AccountSettingsControllerProvider
|
||||
extends $AsyncNotifierProvider<AccountSettingsController, void> {
|
||||
const AccountSettingsControllerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'accountSettingsControllerProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$accountSettingsControllerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
AccountSettingsController create() => AccountSettingsController();
|
||||
}
|
||||
|
||||
String _$accountSettingsControllerHash() =>
|
||||
r'8ca0c05ca6f2d5696126f5ab7ade83419c544afe';
|
||||
|
||||
abstract class _$AccountSettingsController extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build();
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/features/account_settings/presentation/state/account_settings_view_state.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_auth/legacy_auth.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
final accountSettingsViewModelProvider =
|
||||
NotifierProvider.autoDispose<
|
||||
AccountSettingsViewModel,
|
||||
AccountSettingsViewState
|
||||
>(AccountSettingsViewModel.new);
|
||||
|
||||
class AccountSettingsViewModel extends Notifier<AccountSettingsViewState> {
|
||||
late final SfTrackingRepository _tracking;
|
||||
|
||||
@override
|
||||
AccountSettingsViewState build() {
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
return const AccountSettingsViewState();
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
if (state.isLoggingOut) return;
|
||||
|
||||
state = state.copyWith(isLoggingOut: true, errorMessage: '');
|
||||
|
||||
try {
|
||||
await ref.read(legacyAuthRepositoryProvider).logout();
|
||||
} catch (_) {}
|
||||
|
||||
await clearSessionData();
|
||||
|
||||
unawaited(_tracking.legacyAuthLogout());
|
||||
|
||||
state = state.copyWith(isLoggingOut: false);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'account_settings_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class AccountSettingsViewState with _$AccountSettingsViewState {
|
||||
const factory AccountSettingsViewState({
|
||||
@Default(false) bool isLoggingOut,
|
||||
@Default('') String errorMessage,
|
||||
}) = _AccountSettingsViewState;
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
// 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 'account_settings_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AccountSettingsViewState {
|
||||
|
||||
bool get isLoggingOut; String get errorMessage;
|
||||
/// Create a copy of AccountSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$AccountSettingsViewStateCopyWith<AccountSettingsViewState> get copyWith => _$AccountSettingsViewStateCopyWithImpl<AccountSettingsViewState>(this as AccountSettingsViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AccountSettingsViewState&&(identical(other.isLoggingOut, isLoggingOut) || other.isLoggingOut == isLoggingOut)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoggingOut,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AccountSettingsViewState(isLoggingOut: $isLoggingOut, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $AccountSettingsViewStateCopyWith<$Res> {
|
||||
factory $AccountSettingsViewStateCopyWith(AccountSettingsViewState value, $Res Function(AccountSettingsViewState) _then) = _$AccountSettingsViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isLoggingOut, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$AccountSettingsViewStateCopyWithImpl<$Res>
|
||||
implements $AccountSettingsViewStateCopyWith<$Res> {
|
||||
_$AccountSettingsViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final AccountSettingsViewState _self;
|
||||
final $Res Function(AccountSettingsViewState) _then;
|
||||
|
||||
/// Create a copy of AccountSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isLoggingOut = null,Object? errorMessage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
isLoggingOut: null == isLoggingOut ? _self.isLoggingOut : isLoggingOut // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [AccountSettingsViewState].
|
||||
extension AccountSettingsViewStatePatterns on AccountSettingsViewState {
|
||||
/// 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( _AccountSettingsViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState() 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( _AccountSettingsViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState():
|
||||
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( _AccountSettingsViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState() 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( bool isLoggingOut, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState() when $default != null:
|
||||
return $default(_that.isLoggingOut,_that.errorMessage);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( bool isLoggingOut, String errorMessage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState():
|
||||
return $default(_that.isLoggingOut,_that.errorMessage);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( bool isLoggingOut, String errorMessage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AccountSettingsViewState() when $default != null:
|
||||
return $default(_that.isLoggingOut,_that.errorMessage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _AccountSettingsViewState implements AccountSettingsViewState {
|
||||
const _AccountSettingsViewState({this.isLoggingOut = false, this.errorMessage = ''});
|
||||
|
||||
|
||||
@override@JsonKey() final bool isLoggingOut;
|
||||
@override@JsonKey() final String errorMessage;
|
||||
|
||||
/// Create a copy of AccountSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$AccountSettingsViewStateCopyWith<_AccountSettingsViewState> get copyWith => __$AccountSettingsViewStateCopyWithImpl<_AccountSettingsViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AccountSettingsViewState&&(identical(other.isLoggingOut, isLoggingOut) || other.isLoggingOut == isLoggingOut)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoggingOut,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AccountSettingsViewState(isLoggingOut: $isLoggingOut, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$AccountSettingsViewStateCopyWith<$Res> implements $AccountSettingsViewStateCopyWith<$Res> {
|
||||
factory _$AccountSettingsViewStateCopyWith(_AccountSettingsViewState value, $Res Function(_AccountSettingsViewState) _then) = __$AccountSettingsViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isLoggingOut, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$AccountSettingsViewStateCopyWithImpl<$Res>
|
||||
implements _$AccountSettingsViewStateCopyWith<$Res> {
|
||||
__$AccountSettingsViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _AccountSettingsViewState _self;
|
||||
final $Res Function(_AccountSettingsViewState) _then;
|
||||
|
||||
/// Create a copy of AccountSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isLoggingOut = null,Object? errorMessage = null,}) {
|
||||
return _then(_AccountSettingsViewState(
|
||||
isLoggingOut: null == isLoggingOut ? _self.isLoggingOut : isLoggingOut // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:account/src/features/app_users/presentation/state/app_users_view_model.dart';
|
||||
import 'package:account/src/features/app_users/presentation/providers/app_users_edit_mode_provider.dart';
|
||||
import 'package:account/src/features/app_users/presentation/providers/app_users_provider.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:legacy_ui/legacy_ui.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
@@ -15,36 +17,38 @@ class AppUsersScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(appUsersViewModelProvider.notifier);
|
||||
final state = ref.watch(appUsersViewModelProvider);
|
||||
final usersAsync = ref.watch(appUsersProvider);
|
||||
final isEditing = ref.watch(appUsersEditModeProvider);
|
||||
final toggleEditing = ref.read(appUsersEditModeProvider.notifier).toggle;
|
||||
|
||||
final theme = ref.watch(themePortProvider);
|
||||
ref.listen(appUsersProvider, (_, next) => next.showErrorOn(context));
|
||||
|
||||
return LegacyPageLayout(
|
||||
theme: theme,
|
||||
showEdit: true,
|
||||
onEditChange: vm.toggleIsEditing,
|
||||
onEditChange: toggleEditing,
|
||||
title: context.translate(I18n.appUsers),
|
||||
body: Container(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
),
|
||||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) => AppUserCard(
|
||||
user: state.appUsers[index],
|
||||
isEditing: state.isEditing,
|
||||
child: usersAsync.when(
|
||||
data: (users) => ListView.separated(
|
||||
itemBuilder: (_, index) =>
|
||||
AppUserCard(user: users[index], isEditing: isEditing),
|
||||
separatorBuilder: (_, __) =>
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 18, big: 17)),
|
||||
itemCount: users.length,
|
||||
),
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 18, big: 17)),
|
||||
itemCount: state.appUsers.length,
|
||||
loading: () => const LegacyLoadingIndicator(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
footer: state.isEditing
|
||||
footer: isEditing
|
||||
? PrimaryButton(
|
||||
onPressed: vm.toggleIsEditing,
|
||||
onPressed: toggleEditing,
|
||||
text: context.translate(I18n.save),
|
||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
height: SizeUtils.getByScreen(small: 44, big: 42),
|
||||
)
|
||||
: null,
|
||||
@@ -60,8 +64,6 @@ class AppUserCard extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return Container(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
@@ -71,20 +73,20 @@ class AppUserCard extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(SizeUtils.getByScreen(small: 12, big: 18)),
|
||||
),
|
||||
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
padding: EdgeInsets.all(SizeUtils.getByScreen(small: 4, big: 12)),
|
||||
child: Icon(
|
||||
SFIcons.account,
|
||||
size: SizeUtils.getByScreen(small: 40, big: 44),
|
||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
weight: 30,
|
||||
),
|
||||
),
|
||||
@@ -121,12 +123,12 @@ class AppUserCard extends ConsumerWidget {
|
||||
if (isEditing) ...[
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFFF5D52),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
showLegacyDialog(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
child: Container(
|
||||
@@ -166,9 +168,7 @@ class AppUserCard extends ConsumerWidget {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: context.translate(I18n.cancel),
|
||||
color: theme.getColorFor(
|
||||
ThemeCode.legacyPrimary,
|
||||
),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
height: SizeUtils.getByScreen(
|
||||
small: 38,
|
||||
big: 36,
|
||||
@@ -191,9 +191,7 @@ class AppUserCard extends ConsumerWidget {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: context.translate(I18n.delete),
|
||||
color: theme.getColorFor(
|
||||
ThemeCode.legacyPrimary,
|
||||
),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
height: SizeUtils.getByScreen(
|
||||
small: 38,
|
||||
big: 36,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'app_users_edit_mode_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class AppUsersEditMode extends _$AppUsersEditMode {
|
||||
@override
|
||||
bool build() => false;
|
||||
|
||||
void toggle() => state = !state;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'app_users_edit_mode_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AppUsersEditMode)
|
||||
const appUsersEditModeProvider = AppUsersEditModeProvider._();
|
||||
|
||||
final class AppUsersEditModeProvider
|
||||
extends $NotifierProvider<AppUsersEditMode, bool> {
|
||||
const AppUsersEditModeProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'appUsersEditModeProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$appUsersEditModeHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
AppUsersEditMode create() => AppUsersEditMode();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(bool value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<bool>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$appUsersEditModeHash() => r'd4c3717c5dca1dc16bc5846842877967e360e081';
|
||||
|
||||
abstract class _$AppUsersEditMode extends $Notifier<bool> {
|
||||
bool build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<bool, bool>,
|
||||
bool,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:account/src/core/providers/users_repository_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
part 'app_users_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<UserEntity>> appUsers(Ref ref) async {
|
||||
final user = await ref.watch(userInfoProvider.future);
|
||||
return ref.watch(usersRepositoryProvider).getUsers(userId: user.id);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'app_users_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(appUsers)
|
||||
const appUsersProvider = AppUsersProvider._();
|
||||
|
||||
final class AppUsersProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<UserEntity>>,
|
||||
List<UserEntity>,
|
||||
FutureOr<List<UserEntity>>
|
||||
>
|
||||
with $FutureModifier<List<UserEntity>>, $FutureProvider<List<UserEntity>> {
|
||||
const AppUsersProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'appUsersProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$appUsersHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<UserEntity>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<UserEntity>> create(Ref ref) {
|
||||
return appUsers(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$appUsersHash() => r'341fe226d4a606a33e023ee3d93b36100a82be0c';
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/core/domain/repositories/users_repository.dart';
|
||||
import 'package:account/src/core/providers/users_repository_provider.dart';
|
||||
import 'package:account/src/features/app_users/presentation/state/app_users_view_state.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
final appUsersViewModelProvider =
|
||||
NotifierProvider.autoDispose<AppUsersViewModel, AppUsersViewState>(
|
||||
AppUsersViewModel.new,
|
||||
);
|
||||
|
||||
class AppUsersViewModel extends Notifier<AppUsersViewState> {
|
||||
late final UsersRepository _usersRepository;
|
||||
late final SfTrackingRepository _tracking;
|
||||
|
||||
@override
|
||||
AppUsersViewState build() {
|
||||
_usersRepository = ref.read(usersRepositoryProvider);
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
|
||||
_init();
|
||||
|
||||
return const AppUsersViewState();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
|
||||
setUser(user);
|
||||
final appUsers = await _usersRepository.getUsers(userId: user.id);
|
||||
setAppUsers(appUsers);
|
||||
}
|
||||
|
||||
void setUser(UserEntity user) {
|
||||
state = state.copyWith(loggedUser: user);
|
||||
}
|
||||
|
||||
void setAppUsers(List<UserEntity> appUsers) {
|
||||
state = state.copyWith(appUsers: appUsers, isLoading: false);
|
||||
}
|
||||
|
||||
void toggleIsEditing() {
|
||||
state = state.copyWith(isEditing: !state.isEditing);
|
||||
}
|
||||
|
||||
void deleteUser() {
|
||||
unawaited(_tracking.legacyAccountAppUserDeleteTriggered());
|
||||
_usersRepository.deleteUser(userId: state.loggedUser!.id);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
part 'app_users_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class AppUsersViewState with _$AppUsersViewState {
|
||||
const factory AppUsersViewState({
|
||||
@Default(false) bool isLoading,
|
||||
@Default(null) UserEntity? loggedUser,
|
||||
@Default([]) List<UserEntity> appUsers,
|
||||
@Default(false) bool isEditing,
|
||||
@Default('') String errorMessage,
|
||||
}) = _AppUsersViewState;
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
// 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 'app_users_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AppUsersViewState {
|
||||
|
||||
bool get isLoading; UserEntity? get loggedUser; List<UserEntity> get appUsers; bool get isEditing; String get errorMessage;
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$AppUsersViewStateCopyWith<AppUsersViewState> get copyWith => _$AppUsersViewStateCopyWithImpl<AppUsersViewState>(this as AppUsersViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppUsersViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.loggedUser, loggedUser) || other.loggedUser == loggedUser)&&const DeepCollectionEquality().equals(other.appUsers, appUsers)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoading,loggedUser,const DeepCollectionEquality().hash(appUsers),isEditing,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppUsersViewState(isLoading: $isLoading, loggedUser: $loggedUser, appUsers: $appUsers, isEditing: $isEditing, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $AppUsersViewStateCopyWith<$Res> {
|
||||
factory $AppUsersViewStateCopyWith(AppUsersViewState value, $Res Function(AppUsersViewState) _then) = _$AppUsersViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isLoading, UserEntity? loggedUser, List<UserEntity> appUsers, bool isEditing, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
$UserEntityCopyWith<$Res>? get loggedUser;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$AppUsersViewStateCopyWithImpl<$Res>
|
||||
implements $AppUsersViewStateCopyWith<$Res> {
|
||||
_$AppUsersViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final AppUsersViewState _self;
|
||||
final $Res Function(AppUsersViewState) _then;
|
||||
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? loggedUser = freezed,Object? appUsers = null,Object? isEditing = null,Object? errorMessage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,loggedUser: freezed == loggedUser ? _self.loggedUser : loggedUser // ignore: cast_nullable_to_non_nullable
|
||||
as UserEntity?,appUsers: null == appUsers ? _self.appUsers : appUsers // ignore: cast_nullable_to_non_nullable
|
||||
as List<UserEntity>,isEditing: null == isEditing ? _self.isEditing : isEditing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserEntityCopyWith<$Res>? get loggedUser {
|
||||
if (_self.loggedUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $UserEntityCopyWith<$Res>(_self.loggedUser!, (value) {
|
||||
return _then(_self.copyWith(loggedUser: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [AppUsersViewState].
|
||||
extension AppUsersViewStatePatterns on AppUsersViewState {
|
||||
/// 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( _AppUsersViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState() 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( _AppUsersViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState():
|
||||
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( _AppUsersViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState() 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( bool isLoading, UserEntity? loggedUser, List<UserEntity> appUsers, bool isEditing, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState() when $default != null:
|
||||
return $default(_that.isLoading,_that.loggedUser,_that.appUsers,_that.isEditing,_that.errorMessage);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( bool isLoading, UserEntity? loggedUser, List<UserEntity> appUsers, bool isEditing, String errorMessage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState():
|
||||
return $default(_that.isLoading,_that.loggedUser,_that.appUsers,_that.isEditing,_that.errorMessage);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( bool isLoading, UserEntity? loggedUser, List<UserEntity> appUsers, bool isEditing, String errorMessage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _AppUsersViewState() when $default != null:
|
||||
return $default(_that.isLoading,_that.loggedUser,_that.appUsers,_that.isEditing,_that.errorMessage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _AppUsersViewState implements AppUsersViewState {
|
||||
const _AppUsersViewState({this.isLoading = false, this.loggedUser = null, final List<UserEntity> appUsers = const [], this.isEditing = false, this.errorMessage = ''}): _appUsers = appUsers;
|
||||
|
||||
|
||||
@override@JsonKey() final bool isLoading;
|
||||
@override@JsonKey() final UserEntity? loggedUser;
|
||||
final List<UserEntity> _appUsers;
|
||||
@override@JsonKey() List<UserEntity> get appUsers {
|
||||
if (_appUsers is EqualUnmodifiableListView) return _appUsers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_appUsers);
|
||||
}
|
||||
|
||||
@override@JsonKey() final bool isEditing;
|
||||
@override@JsonKey() final String errorMessage;
|
||||
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$AppUsersViewStateCopyWith<_AppUsersViewState> get copyWith => __$AppUsersViewStateCopyWithImpl<_AppUsersViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppUsersViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.loggedUser, loggedUser) || other.loggedUser == loggedUser)&&const DeepCollectionEquality().equals(other._appUsers, _appUsers)&&(identical(other.isEditing, isEditing) || other.isEditing == isEditing)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoading,loggedUser,const DeepCollectionEquality().hash(_appUsers),isEditing,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppUsersViewState(isLoading: $isLoading, loggedUser: $loggedUser, appUsers: $appUsers, isEditing: $isEditing, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$AppUsersViewStateCopyWith<$Res> implements $AppUsersViewStateCopyWith<$Res> {
|
||||
factory _$AppUsersViewStateCopyWith(_AppUsersViewState value, $Res Function(_AppUsersViewState) _then) = __$AppUsersViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isLoading, UserEntity? loggedUser, List<UserEntity> appUsers, bool isEditing, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
@override $UserEntityCopyWith<$Res>? get loggedUser;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$AppUsersViewStateCopyWithImpl<$Res>
|
||||
implements _$AppUsersViewStateCopyWith<$Res> {
|
||||
__$AppUsersViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _AppUsersViewState _self;
|
||||
final $Res Function(_AppUsersViewState) _then;
|
||||
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? loggedUser = freezed,Object? appUsers = null,Object? isEditing = null,Object? errorMessage = null,}) {
|
||||
return _then(_AppUsersViewState(
|
||||
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,loggedUser: freezed == loggedUser ? _self.loggedUser : loggedUser // ignore: cast_nullable_to_non_nullable
|
||||
as UserEntity?,appUsers: null == appUsers ? _self._appUsers : appUsers // ignore: cast_nullable_to_non_nullable
|
||||
as List<UserEntity>,isEditing: null == isEditing ? _self.isEditing : isEditing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of AppUsersViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserEntityCopyWith<$Res>? get loggedUser {
|
||||
if (_self.loggedUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $UserEntityCopyWith<$Res>(_self.loggedUser!, (value) {
|
||||
return _then(_self.copyWith(loggedUser: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,8 +0,0 @@
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
|
||||
abstract class ChangePasswordUseCase {
|
||||
Future<void> changePassword({
|
||||
required String userId,
|
||||
required ChangePasswordRequestEntity request,
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:account/src/core/domain/repositories/change_password_repository.dart';
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:account/src/features/change_password/domain/change_password_use_case.dart';
|
||||
|
||||
class ChangePasswordUseCaseImpl implements ChangePasswordUseCase {
|
||||
ChangePasswordUseCaseImpl(this._repository);
|
||||
|
||||
final ChangePasswordRepository _repository;
|
||||
|
||||
@override
|
||||
Future<void> changePassword({
|
||||
required String userId,
|
||||
required ChangePasswordRequestEntity request,
|
||||
}) {
|
||||
return _repository.changePassword(userId: userId, request: request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class PasswordValidator {
|
||||
const PasswordValidator._();
|
||||
|
||||
static final _upperRegex = RegExp(r'[A-Z]');
|
||||
static final _digitRegex = RegExp(r'[0-9]');
|
||||
static final _specialRegex = RegExp(r'[!@#$%^&*(),.?":{}|<>\-_+=\[\]\\\/~`]');
|
||||
|
||||
static const int minLength = 8;
|
||||
|
||||
static String? validate({
|
||||
required String password,
|
||||
required String repeat,
|
||||
}) {
|
||||
final trimmed = password.trim();
|
||||
final trimmedRepeat = repeat.trim();
|
||||
|
||||
if (trimmed.isEmpty) return I18n.errorPasswordRequired;
|
||||
if (trimmedRepeat.isEmpty) return I18n.errorPasswordRequired;
|
||||
if (trimmed != trimmedRepeat) return I18n.errorMessageUnequalPasswords;
|
||||
if (trimmed.length < minLength) return I18n.errorMessagePasswordTooShort;
|
||||
if (!_upperRegex.hasMatch(trimmed)) {
|
||||
return I18n.errorMessagePasswordNoCapitals;
|
||||
}
|
||||
if (!_digitRegex.hasMatch(trimmed)) {
|
||||
return I18n.errorMessagePasswordNoNumbers;
|
||||
}
|
||||
if (!_specialRegex.hasMatch(trimmed)) {
|
||||
return I18n.errorMessagePasswordNoSpecialChars;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,76 @@
|
||||
import 'package:account/src/features/change_password/presentation/state/change_password_view_model.dart';
|
||||
import 'package:account/src/features/change_password/domain/password_validator.dart';
|
||||
import 'package:account/src/features/change_password/presentation/providers/change_password_controller.dart';
|
||||
import 'package:account/src/features/change_password/presentation/providers/change_password_local_error_provider.dart';
|
||||
import 'package:account/src/features/change_password/presentation/providers/change_password_visibility_provider.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:legacy_ui/legacy_ui.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
class ChangePasswordScreen extends ConsumerWidget {
|
||||
class ChangePasswordScreen extends ConsumerStatefulWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const ChangePasswordScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
final password = ref.watch(
|
||||
changePasswordViewModelProvider.select((s) => s.newPassword),
|
||||
ConsumerState<ChangePasswordScreen> createState() =>
|
||||
_ChangePasswordScreenState();
|
||||
}
|
||||
|
||||
class _ChangePasswordScreenState extends ConsumerState<ChangePasswordScreen> {
|
||||
late final TextEditingController _newPasswordController;
|
||||
late final TextEditingController _repeatPasswordController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_newPasswordController = TextEditingController();
|
||||
_repeatPasswordController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_newPasswordController.dispose();
|
||||
_repeatPasswordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSubmit() {
|
||||
final error = PasswordValidator.validate(
|
||||
password: _newPasswordController.text,
|
||||
repeat: _repeatPasswordController.text,
|
||||
);
|
||||
ref.read(changePasswordLocalErrorProvider.notifier).set(error);
|
||||
if (error != null) return;
|
||||
ref
|
||||
.read(changePasswordControllerProvider.notifier)
|
||||
.submit(password: _newPasswordController.text.trim());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(changePasswordControllerProvider, (prev, next) async {
|
||||
next.showErrorOn(context);
|
||||
if (prev != null &&
|
||||
prev.isLoading &&
|
||||
!next.isLoading &&
|
||||
!next.hasError) {
|
||||
await showSuccessDialog(context, I18n.passwordChangedSuccess);
|
||||
if (context.mounted) widget.navigationContract.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
final isLoading = ref.watch(
|
||||
changePasswordControllerProvider.select((s) => s.isLoading),
|
||||
);
|
||||
final localError = ref.watch(changePasswordLocalErrorProvider);
|
||||
|
||||
return LegacyPageLayout(
|
||||
theme: theme,
|
||||
title: context.translate(I18n.changePassword),
|
||||
body: Container(
|
||||
padding: SizeUtils.getByScreen(
|
||||
@@ -31,65 +81,90 @@ class ChangePasswordScreen extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const _NewPasswordSection(),
|
||||
_NewPasswordField(controller: _newPasswordController),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 22)),
|
||||
const _RepeatPasswordSection(),
|
||||
_RepeatPasswordField(controller: _repeatPasswordController),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 22)),
|
||||
_PasswordCriteriaList(password: password),
|
||||
const _ErrorMessageSection(),
|
||||
_PasswordCriteriaList(
|
||||
newController: _newPasswordController,
|
||||
repeatController: _repeatPasswordController,
|
||||
),
|
||||
if (localError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
context.translate(localError),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: _SaveSection(navigationContract: navigationContract),
|
||||
footer: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||
child: PrimaryButton(
|
||||
onPressed: isLoading ? null : _onSubmit,
|
||||
text: context.translate(I18n.save),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NewPasswordSection extends ConsumerWidget {
|
||||
const _NewPasswordSection();
|
||||
class _NewPasswordField extends ConsumerWidget {
|
||||
final TextEditingController controller;
|
||||
|
||||
const _NewPasswordField({required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(changePasswordViewModelProvider.notifier);
|
||||
final showPassword = ref.watch(
|
||||
changePasswordViewModelProvider.select((s) => s.showCurrentPassword),
|
||||
);
|
||||
|
||||
final visibility = ref.watch(changePasswordVisibilityProvider);
|
||||
return CustomTextField(
|
||||
controller: vm.newPasswordController,
|
||||
controller: controller,
|
||||
hint: '********',
|
||||
label: context.translate(I18n.newPassword),
|
||||
showPassword: showPassword,
|
||||
onVisibilityChanged: vm.toggleNewPasswordVisibility,
|
||||
showPassword: visibility.showNew,
|
||||
onVisibilityChanged: ref
|
||||
.read(changePasswordVisibilityProvider.notifier)
|
||||
.toggleNew,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RepeatPasswordSection extends ConsumerWidget {
|
||||
const _RepeatPasswordSection();
|
||||
class _RepeatPasswordField extends ConsumerWidget {
|
||||
final TextEditingController controller;
|
||||
|
||||
const _RepeatPasswordField({required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final vm = ref.read(changePasswordViewModelProvider.notifier);
|
||||
final showPassword = ref.watch(
|
||||
changePasswordViewModelProvider.select((s) => s.showCurrentPassword),
|
||||
);
|
||||
|
||||
final visibility = ref.watch(changePasswordVisibilityProvider);
|
||||
return CustomTextField(
|
||||
controller: vm.repeatPasswordController,
|
||||
controller: controller,
|
||||
hint: '********',
|
||||
label: context.translate(I18n.repeatPassword),
|
||||
showPassword: showPassword,
|
||||
onVisibilityChanged: vm.toggleRepeatedPasswordVisibility,
|
||||
showPassword: visibility.showRepeated,
|
||||
onVisibilityChanged: ref
|
||||
.read(changePasswordVisibilityProvider.notifier)
|
||||
.toggleRepeated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PasswordCriteriaList extends StatelessWidget {
|
||||
final String password;
|
||||
final TextEditingController newController;
|
||||
final TextEditingController repeatController;
|
||||
|
||||
const _PasswordCriteriaList({required this.password});
|
||||
const _PasswordCriteriaList({
|
||||
required this.newController,
|
||||
required this.repeatController,
|
||||
});
|
||||
|
||||
static final _upperRegex = RegExp(r'[A-Z]');
|
||||
static final _digitRegex = RegExp(r'[0-9]');
|
||||
@@ -97,33 +172,45 @@ class _PasswordCriteriaList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasInput = password.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 6,
|
||||
children: [
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordLength),
|
||||
met: password.length >= 8,
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordCapital),
|
||||
met: _upperRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordNumber),
|
||||
met: _digitRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordSpecial),
|
||||
met: _specialRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
],
|
||||
return ListenableBuilder(
|
||||
listenable: Listenable.merge([newController, repeatController]),
|
||||
builder: (_, __) {
|
||||
final password = newController.text;
|
||||
final repeat = repeatController.text;
|
||||
final hasInput = password.isNotEmpty;
|
||||
final hasRepeatInput = repeat.isNotEmpty;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 6,
|
||||
children: [
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordLength),
|
||||
met: password.length >= PasswordValidator.minLength,
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordCapital),
|
||||
met: _upperRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordNumber),
|
||||
met: _digitRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordSpecial),
|
||||
met: _specialRegex.hasMatch(password),
|
||||
hasInput: hasInput,
|
||||
),
|
||||
_CriteriaRow(
|
||||
label: context.translate(I18n.passwordMatch),
|
||||
met: password.isNotEmpty && password == repeat,
|
||||
hasInput: hasInput && hasRepeatInput,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -151,7 +238,7 @@ class _CriteriaRow extends StatelessWidget {
|
||||
color = Colors.green;
|
||||
icon = Icons.check_circle;
|
||||
} else {
|
||||
color = Colors.red.shade400;
|
||||
color = Theme.of(context).colorScheme.error;
|
||||
icon = Icons.cancel;
|
||||
}
|
||||
|
||||
@@ -164,76 +251,3 @@ class _CriteriaRow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ErrorMessageSection extends ConsumerWidget {
|
||||
const _ErrorMessageSection();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final errorMessage = ref.watch(
|
||||
changePasswordViewModelProvider.select((s) => s.errorMessage),
|
||||
);
|
||||
|
||||
if (errorMessage.isNotEmpty) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
errorMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Color.fromRGBO(239, 17, 17, 1),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SaveSection extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const _SaveSection({required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.read(themePortProvider);
|
||||
|
||||
final vm = ref.read(changePasswordViewModelProvider.notifier);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
await vm.submit();
|
||||
if (!context.mounted) return;
|
||||
|
||||
final errorMessage = ref.read(
|
||||
changePasswordViewModelProvider.select((s) => s.errorMessage),
|
||||
);
|
||||
if (errorMessage.isNotEmpty) {
|
||||
showTopSnackbar(
|
||||
context,
|
||||
message: errorMessage,
|
||||
type: MessageType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final isComplete = ref.read(
|
||||
changePasswordViewModelProvider.select((s) => s.isComplete),
|
||||
);
|
||||
if (isComplete) {
|
||||
navigationContract.goTo(AppRoutes.legacyLogin);
|
||||
}
|
||||
},
|
||||
text: context.translate(I18n.save),
|
||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/core/providers/change_password_repository_provider.dart';
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
part 'change_password_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class ChangePasswordController extends _$ChangePasswordController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> submit({required String password}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
await ref.read(changePasswordRepositoryProvider).changePassword(
|
||||
userId: user.id,
|
||||
request: ChangePasswordRequestEntity(password: password),
|
||||
);
|
||||
unawaited(ref.read(sfTrackingProvider).legacyAccountPasswordChanged());
|
||||
});
|
||||
if (state.hasError) {
|
||||
unawaited(
|
||||
ref
|
||||
.read(sfTrackingProvider)
|
||||
.legacyAccountPasswordChangeFailed(state.error.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'change_password_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ChangePasswordController)
|
||||
const changePasswordControllerProvider = ChangePasswordControllerProvider._();
|
||||
|
||||
final class ChangePasswordControllerProvider
|
||||
extends $AsyncNotifierProvider<ChangePasswordController, void> {
|
||||
const ChangePasswordControllerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'changePasswordControllerProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$changePasswordControllerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ChangePasswordController create() => ChangePasswordController();
|
||||
}
|
||||
|
||||
String _$changePasswordControllerHash() =>
|
||||
r'45645f3f5759459d1f048b0e8e553b9908e659d9';
|
||||
|
||||
abstract class _$ChangePasswordController extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build();
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'change_password_local_error_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class ChangePasswordLocalError extends _$ChangePasswordLocalError {
|
||||
@override
|
||||
String? build() => null;
|
||||
|
||||
void set(String? value) => state = value;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'change_password_local_error_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ChangePasswordLocalError)
|
||||
const changePasswordLocalErrorProvider = ChangePasswordLocalErrorProvider._();
|
||||
|
||||
final class ChangePasswordLocalErrorProvider
|
||||
extends $NotifierProvider<ChangePasswordLocalError, String?> {
|
||||
const ChangePasswordLocalErrorProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'changePasswordLocalErrorProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$changePasswordLocalErrorHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ChangePasswordLocalError create() => ChangePasswordLocalError();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(String? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<String?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$changePasswordLocalErrorHash() =>
|
||||
r'a8cc23f326988bbdc13b058e055daaa8c684a8c8';
|
||||
|
||||
abstract class _$ChangePasswordLocalError extends $Notifier<String?> {
|
||||
String? build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<String?, String?>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<String?, String?>,
|
||||
String?,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import 'package:account/src/features/change_password/domain/change_password_use_case.dart';
|
||||
import 'package:account/src/features/change_password/domain/change_password_use_case_impl.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../core/providers/change_password_repository_provider.dart';
|
||||
|
||||
final changePasswordUseCaseProvider =
|
||||
Provider.autoDispose<ChangePasswordUseCase>((ref) {
|
||||
final changePassword = ref.read(changePasswordRepositoryProvider);
|
||||
return ChangePasswordUseCaseImpl(changePassword);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'change_password_visibility_provider.g.dart';
|
||||
|
||||
class ChangePasswordVisibility {
|
||||
const ChangePasswordVisibility({
|
||||
this.showNew = false,
|
||||
this.showRepeated = false,
|
||||
});
|
||||
|
||||
final bool showNew;
|
||||
final bool showRepeated;
|
||||
|
||||
ChangePasswordVisibility copyWith({bool? showNew, bool? showRepeated}) {
|
||||
return ChangePasswordVisibility(
|
||||
showNew: showNew ?? this.showNew,
|
||||
showRepeated: showRepeated ?? this.showRepeated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ChangePasswordVisibilityNotifier
|
||||
extends _$ChangePasswordVisibilityNotifier {
|
||||
@override
|
||||
ChangePasswordVisibility build() => const ChangePasswordVisibility();
|
||||
|
||||
void toggleNew() => state = state.copyWith(showNew: !state.showNew);
|
||||
|
||||
void toggleRepeated() =>
|
||||
state = state.copyWith(showRepeated: !state.showRepeated);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'change_password_visibility_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ChangePasswordVisibilityNotifier)
|
||||
const changePasswordVisibilityProvider =
|
||||
ChangePasswordVisibilityNotifierProvider._();
|
||||
|
||||
final class ChangePasswordVisibilityNotifierProvider
|
||||
extends
|
||||
$NotifierProvider<
|
||||
ChangePasswordVisibilityNotifier,
|
||||
ChangePasswordVisibility
|
||||
> {
|
||||
const ChangePasswordVisibilityNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'changePasswordVisibilityProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$changePasswordVisibilityNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ChangePasswordVisibilityNotifier create() =>
|
||||
ChangePasswordVisibilityNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(ChangePasswordVisibility value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<ChangePasswordVisibility>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$changePasswordVisibilityNotifierHash() =>
|
||||
r'8b4a81d152e0982675608d0ba3dbd53c93257d17';
|
||||
|
||||
abstract class _$ChangePasswordVisibilityNotifier
|
||||
extends $Notifier<ChangePasswordVisibility> {
|
||||
ChangePasswordVisibility build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref =
|
||||
this.ref as $Ref<ChangePasswordVisibility, ChangePasswordVisibility>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<ChangePasswordVisibility, ChangePasswordVisibility>,
|
||||
ChangePasswordVisibility,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/features/change_password/domain/change_password_use_case.dart';
|
||||
import 'package:account/src/features/change_password/domain/models/entities/change_password_request_entity.dart';
|
||||
import 'package:account/src/features/change_password/presentation/providers/change_password_use_case_provider.dart';
|
||||
import 'package:account/src/features/change_password/presentation/state/change_password_view_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
final changePasswordViewModelProvider =
|
||||
NotifierProvider.autoDispose<
|
||||
ChangePasswordViewModel,
|
||||
ChangePasswordViewState
|
||||
>(ChangePasswordViewModel.new);
|
||||
|
||||
class ChangePasswordViewModel extends Notifier<ChangePasswordViewState> {
|
||||
late final ChangePasswordUseCase _changePasswordUseCase;
|
||||
late final SfTrackingRepository _tracking;
|
||||
|
||||
late final TextEditingController newPasswordController;
|
||||
late final TextEditingController repeatPasswordController;
|
||||
late final TextEditingController passwordController;
|
||||
|
||||
@override
|
||||
ChangePasswordViewState build() {
|
||||
_changePasswordUseCase = ref.read(changePasswordUseCaseProvider);
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
|
||||
_initControllers();
|
||||
|
||||
return const ChangePasswordViewState();
|
||||
}
|
||||
|
||||
void _initControllers() {
|
||||
newPasswordController = TextEditingController();
|
||||
newPasswordController.addListener(_onNewPasswordChanged);
|
||||
|
||||
repeatPasswordController = TextEditingController();
|
||||
repeatPasswordController.addListener(_onRepeatPasswordChanged);
|
||||
|
||||
ref.onDispose(disposeControllers);
|
||||
}
|
||||
|
||||
void toggleCurrentPasswordVisibility() {
|
||||
state = state.copyWith(showCurrentPassword: !state.showCurrentPassword);
|
||||
}
|
||||
|
||||
void toggleNewPasswordVisibility() {
|
||||
state = state.copyWith(showNewPassword: !state.showNewPassword);
|
||||
}
|
||||
|
||||
void toggleRepeatedPasswordVisibility() {
|
||||
state = state.copyWith(showRepeatedPassword: !state.showRepeatedPassword);
|
||||
}
|
||||
|
||||
void _onNewPasswordChanged() {
|
||||
final value = newPasswordController.text;
|
||||
|
||||
if (value == state.newPassword) return;
|
||||
|
||||
state = state.copyWith(newPassword: value, errorMessage: '');
|
||||
}
|
||||
|
||||
void _onRepeatPasswordChanged() {
|
||||
final value = repeatPasswordController.text;
|
||||
|
||||
if (value == state.repeatPassword) return;
|
||||
|
||||
state = state.copyWith(repeatPassword: value, errorMessage: '');
|
||||
}
|
||||
|
||||
bool _validateForm() {
|
||||
final upperRegex = RegExp(r'[A-Z]');
|
||||
final digitRegex = RegExp(r'[0-9]');
|
||||
final specialRegex = RegExp(r'[!@#$%^&*(),.?":{}|<>\-_+=\[\]\\\/~`]');
|
||||
|
||||
final password = state.newPassword.trim();
|
||||
|
||||
if (password.isEmpty) {
|
||||
state = state.copyWith(errorMessage: 'errorMessageNewPasswordIsEmpty');
|
||||
return false;
|
||||
}
|
||||
if (state.repeatPassword.trim().isEmpty) {
|
||||
state = state.copyWith(errorMessage: 'errorMessageRepeatPasswordIsEmpty');
|
||||
return false;
|
||||
}
|
||||
if (password != state.repeatPassword.trim()) {
|
||||
state = state.copyWith(errorMessage: 'errorMessagePasswordsDontMatch');
|
||||
return false;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
state = state.copyWith(errorMessage: 'errorPasswordMinLength');
|
||||
return false;
|
||||
}
|
||||
if (!upperRegex.hasMatch(password)) {
|
||||
state = state.copyWith(errorMessage: 'errorPasswordUppercase');
|
||||
return false;
|
||||
}
|
||||
if (!digitRegex.hasMatch(password)) {
|
||||
state = state.copyWith(errorMessage: 'errorPasswordDigits');
|
||||
return false;
|
||||
}
|
||||
if (!specialRegex.hasMatch(password)) {
|
||||
state = state.copyWith(errorMessage: 'errorPasswordSpecial');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ChangePasswordRequestEntity _toRequest() {
|
||||
return ChangePasswordRequestEntity(password: state.newPassword.trim());
|
||||
}
|
||||
|
||||
Future<void> submit() async {
|
||||
if (state.isLoading) return;
|
||||
if (!_validateForm()) return;
|
||||
|
||||
try {
|
||||
state = state.copyWith(isLoading: true, isComplete: false);
|
||||
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
|
||||
final request = _toRequest();
|
||||
|
||||
await _changePasswordUseCase.changePassword(
|
||||
userId: user.id,
|
||||
request: request,
|
||||
);
|
||||
unawaited(_tracking.legacyAccountPasswordChanged());
|
||||
state = state.copyWith(isLoading: false, isComplete: true);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
_finishWithError(message: e.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _finishWithError({required String message}) {
|
||||
unawaited(_tracking.legacyAccountPasswordChangeFailed(message));
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
isComplete: false,
|
||||
errorMessage: message,
|
||||
);
|
||||
}
|
||||
|
||||
void disposeControllers() {
|
||||
newPasswordController.removeListener(_onNewPasswordChanged);
|
||||
newPasswordController.dispose();
|
||||
|
||||
repeatPasswordController.removeListener(_onRepeatPasswordChanged);
|
||||
repeatPasswordController.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'change_password_view_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class ChangePasswordViewState with _$ChangePasswordViewState {
|
||||
const factory ChangePasswordViewState({
|
||||
@Default(false) bool isLoading,
|
||||
@Default(false) bool isComplete,
|
||||
@Default(false) bool showNewPassword,
|
||||
@Default(false) bool showRepeatedPassword,
|
||||
@Default('') String newPassword,
|
||||
@Default('') String repeatPassword,
|
||||
@Default('') String errorMessage,
|
||||
}) = _ChangePasswordViewState;
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
// 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 'change_password_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ChangePasswordViewState {
|
||||
|
||||
bool get isLoading; bool get isComplete; bool get showCurrentPassword; bool get showNewPassword; bool get showRepeatedPassword; String get currentPassword; String get newPassword; String get repeatPassword; String get errorMessage;
|
||||
/// Create a copy of ChangePasswordViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ChangePasswordViewStateCopyWith<ChangePasswordViewState> get copyWith => _$ChangePasswordViewStateCopyWithImpl<ChangePasswordViewState>(this as ChangePasswordViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChangePasswordViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.showCurrentPassword, showCurrentPassword) || other.showCurrentPassword == showCurrentPassword)&&(identical(other.showNewPassword, showNewPassword) || other.showNewPassword == showNewPassword)&&(identical(other.showRepeatedPassword, showRepeatedPassword) || other.showRepeatedPassword == showRepeatedPassword)&&(identical(other.currentPassword, currentPassword) || other.currentPassword == currentPassword)&&(identical(other.newPassword, newPassword) || other.newPassword == newPassword)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,showCurrentPassword,showNewPassword,showRepeatedPassword,currentPassword,newPassword,repeatPassword,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChangePasswordViewState(isLoading: $isLoading, isComplete: $isComplete, showCurrentPassword: $showCurrentPassword, showNewPassword: $showNewPassword, showRepeatedPassword: $showRepeatedPassword, currentPassword: $currentPassword, newPassword: $newPassword, repeatPassword: $repeatPassword, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ChangePasswordViewStateCopyWith<$Res> {
|
||||
factory $ChangePasswordViewStateCopyWith(ChangePasswordViewState value, $Res Function(ChangePasswordViewState) _then) = _$ChangePasswordViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isLoading, bool isComplete, bool showCurrentPassword, bool showNewPassword, bool showRepeatedPassword, String currentPassword, String newPassword, String repeatPassword, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ChangePasswordViewStateCopyWithImpl<$Res>
|
||||
implements $ChangePasswordViewStateCopyWith<$Res> {
|
||||
_$ChangePasswordViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ChangePasswordViewState _self;
|
||||
final $Res Function(ChangePasswordViewState) _then;
|
||||
|
||||
/// Create a copy of ChangePasswordViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? isComplete = null,Object? showCurrentPassword = null,Object? showNewPassword = null,Object? showRepeatedPassword = null,Object? currentPassword = null,Object? newPassword = null,Object? repeatPassword = null,Object? errorMessage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showCurrentPassword: null == showCurrentPassword ? _self.showCurrentPassword : showCurrentPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showNewPassword: null == showNewPassword ? _self.showNewPassword : showNewPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showRepeatedPassword: null == showRepeatedPassword ? _self.showRepeatedPassword : showRepeatedPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,currentPassword: null == currentPassword ? _self.currentPassword : currentPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,newPassword: null == newPassword ? _self.newPassword : newPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,repeatPassword: null == repeatPassword ? _self.repeatPassword : repeatPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ChangePasswordViewState].
|
||||
extension ChangePasswordViewStatePatterns on ChangePasswordViewState {
|
||||
/// 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( _ChangePasswordViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState() 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( _ChangePasswordViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState():
|
||||
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( _ChangePasswordViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState() 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( bool isLoading, bool isComplete, bool showCurrentPassword, bool showNewPassword, bool showRepeatedPassword, String currentPassword, String newPassword, String repeatPassword, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState() when $default != null:
|
||||
return $default(_that.isLoading,_that.isComplete,_that.showCurrentPassword,_that.showNewPassword,_that.showRepeatedPassword,_that.currentPassword,_that.newPassword,_that.repeatPassword,_that.errorMessage);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( bool isLoading, bool isComplete, bool showCurrentPassword, bool showNewPassword, bool showRepeatedPassword, String currentPassword, String newPassword, String repeatPassword, String errorMessage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState():
|
||||
return $default(_that.isLoading,_that.isComplete,_that.showCurrentPassword,_that.showNewPassword,_that.showRepeatedPassword,_that.currentPassword,_that.newPassword,_that.repeatPassword,_that.errorMessage);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( bool isLoading, bool isComplete, bool showCurrentPassword, bool showNewPassword, bool showRepeatedPassword, String currentPassword, String newPassword, String repeatPassword, String errorMessage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ChangePasswordViewState() when $default != null:
|
||||
return $default(_that.isLoading,_that.isComplete,_that.showCurrentPassword,_that.showNewPassword,_that.showRepeatedPassword,_that.currentPassword,_that.newPassword,_that.repeatPassword,_that.errorMessage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ChangePasswordViewState implements ChangePasswordViewState {
|
||||
const _ChangePasswordViewState({this.isLoading = false, this.isComplete = false, this.showCurrentPassword = false, this.showNewPassword = false, this.showRepeatedPassword = false, this.currentPassword = '', this.newPassword = '', this.repeatPassword = '', this.errorMessage = ''});
|
||||
|
||||
|
||||
@override@JsonKey() final bool isLoading;
|
||||
@override@JsonKey() final bool isComplete;
|
||||
@override@JsonKey() final bool showCurrentPassword;
|
||||
@override@JsonKey() final bool showNewPassword;
|
||||
@override@JsonKey() final bool showRepeatedPassword;
|
||||
@override@JsonKey() final String currentPassword;
|
||||
@override@JsonKey() final String newPassword;
|
||||
@override@JsonKey() final String repeatPassword;
|
||||
@override@JsonKey() final String errorMessage;
|
||||
|
||||
/// Create a copy of ChangePasswordViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ChangePasswordViewStateCopyWith<_ChangePasswordViewState> get copyWith => __$ChangePasswordViewStateCopyWithImpl<_ChangePasswordViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChangePasswordViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.showCurrentPassword, showCurrentPassword) || other.showCurrentPassword == showCurrentPassword)&&(identical(other.showNewPassword, showNewPassword) || other.showNewPassword == showNewPassword)&&(identical(other.showRepeatedPassword, showRepeatedPassword) || other.showRepeatedPassword == showRepeatedPassword)&&(identical(other.currentPassword, currentPassword) || other.currentPassword == currentPassword)&&(identical(other.newPassword, newPassword) || other.newPassword == newPassword)&&(identical(other.repeatPassword, repeatPassword) || other.repeatPassword == repeatPassword)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,showCurrentPassword,showNewPassword,showRepeatedPassword,currentPassword,newPassword,repeatPassword,errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChangePasswordViewState(isLoading: $isLoading, isComplete: $isComplete, showCurrentPassword: $showCurrentPassword, showNewPassword: $showNewPassword, showRepeatedPassword: $showRepeatedPassword, currentPassword: $currentPassword, newPassword: $newPassword, repeatPassword: $repeatPassword, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ChangePasswordViewStateCopyWith<$Res> implements $ChangePasswordViewStateCopyWith<$Res> {
|
||||
factory _$ChangePasswordViewStateCopyWith(_ChangePasswordViewState value, $Res Function(_ChangePasswordViewState) _then) = __$ChangePasswordViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isLoading, bool isComplete, bool showCurrentPassword, bool showNewPassword, bool showRepeatedPassword, String currentPassword, String newPassword, String repeatPassword, String errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ChangePasswordViewStateCopyWithImpl<$Res>
|
||||
implements _$ChangePasswordViewStateCopyWith<$Res> {
|
||||
__$ChangePasswordViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ChangePasswordViewState _self;
|
||||
final $Res Function(_ChangePasswordViewState) _then;
|
||||
|
||||
/// Create a copy of ChangePasswordViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? isComplete = null,Object? showCurrentPassword = null,Object? showNewPassword = null,Object? showRepeatedPassword = null,Object? currentPassword = null,Object? newPassword = null,Object? repeatPassword = null,Object? errorMessage = null,}) {
|
||||
return _then(_ChangePasswordViewState(
|
||||
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showCurrentPassword: null == showCurrentPassword ? _self.showCurrentPassword : showCurrentPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showNewPassword: null == showNewPassword ? _self.showNewPassword : showNewPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,showRepeatedPassword: null == showRepeatedPassword ? _self.showRepeatedPassword : showRepeatedPassword // ignore: cast_nullable_to_non_nullable
|
||||
as bool,currentPassword: null == currentPassword ? _self.currentPassword : currentPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,newPassword: null == newPassword ? _self.newPassword : newPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,repeatPassword: null == repeatPassword ? _self.repeatPassword : repeatPassword // ignore: cast_nullable_to_non_nullable
|
||||
as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:account/src/features/delete_account/presentation/state/delete_account_view_model.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/features/delete_account/presentation/widgets/confirm_dialog.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:legacy_ui/legacy_ui.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
class DeleteAccountScreen extends ConsumerWidget {
|
||||
@@ -15,14 +19,11 @@ class DeleteAccountScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return LegacyPageLayout(
|
||||
theme: theme,
|
||||
title: context.translate(I18n.deleteAccount),
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 10),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
const _Header(),
|
||||
@@ -37,31 +38,29 @@ class DeleteAccountScreen extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends ConsumerWidget {
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.read(themePortProvider);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: theme.getColorFor(ThemeCode.backgroundSecondary),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
width: double.infinity,
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(vertical: 20),
|
||||
big: EdgeInsets.symmetric(vertical: 19),
|
||||
small: const EdgeInsets.symmetric(vertical: 20),
|
||||
big: const EdgeInsets.symmetric(vertical: 19),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFF00A1C6),
|
||||
color: const Color(0xFF00A1C6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.all(7),
|
||||
big: EdgeInsets.all(6),
|
||||
small: const EdgeInsets.all(7),
|
||||
big: const EdgeInsets.all(6),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.power_settings_new_outlined,
|
||||
@@ -81,13 +80,11 @@ class _Header extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _BodySection extends ConsumerWidget {
|
||||
class _BodySection extends StatelessWidget {
|
||||
const _BodySection();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.read(themePortProvider);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
@@ -100,7 +97,7 @@ class _BodySection extends ConsumerWidget {
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.getColorFor(ThemeCode.textPrimary),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -115,39 +112,38 @@ class _RequestCancelSection extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.read(themePortProvider);
|
||||
|
||||
final isLoading = ref.watch(
|
||||
deleteAccountViewModelProvider.select((s) => s.isLoading),
|
||||
);
|
||||
final user = ref.watch(
|
||||
deleteAccountViewModelProvider.select((s) => s.loggedUser),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
big: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
small: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
big: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
if (isLoading) return;
|
||||
if (user == null) {
|
||||
navigationContract.goTo(AppRoutes.login);
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: ConfirmDialog(navigationContract: navigationContract),
|
||||
),
|
||||
);
|
||||
},
|
||||
onPressed: () => _onDeletePressed(context, ref),
|
||||
text: context.translate(I18n.requestCancelButton),
|
||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||
color: context.sfColors.legacyPrimary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDeletePressed(BuildContext context, WidgetRef ref) async {
|
||||
unawaited(ref.read(sfTrackingProvider).legacyAccountDeletionInitiated());
|
||||
|
||||
final devices = await ref.read(legacyDevicesProvider.future);
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (devices.isNotEmpty) {
|
||||
await showInfoDialog(
|
||||
context,
|
||||
I18n.accountDeletionHasDevices,
|
||||
autoDismiss: const Duration(seconds: 5),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
showLegacyDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => ConfirmDialog(navigationContract: navigationContract),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/core/providers/users_repository_provider.dart';
|
||||
import 'package:legacy_auth/legacy_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
part 'delete_account_controller.g.dart';
|
||||
|
||||
@riverpod
|
||||
class DeleteAccountController extends _$DeleteAccountController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> verifyPassword({required String password}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
await ref
|
||||
.read(legacyLoginRepositoryProvider)
|
||||
.login(email: user.email, password: password);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> submit() async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
unawaited(
|
||||
ref.read(sfTrackingProvider).legacyAccountDeletionConfirmed(),
|
||||
);
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
await ref.read(usersRepositoryProvider).deleteUser(userId: user.id);
|
||||
unawaited(
|
||||
ref.read(sfTrackingProvider).legacyAccountDeletionCompleted(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'delete_account_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(DeleteAccountController)
|
||||
const deleteAccountControllerProvider = DeleteAccountControllerProvider._();
|
||||
|
||||
final class DeleteAccountControllerProvider
|
||||
extends $AsyncNotifierProvider<DeleteAccountController, void> {
|
||||
const DeleteAccountControllerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'deleteAccountControllerProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$deleteAccountControllerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
DeleteAccountController create() => DeleteAccountController();
|
||||
}
|
||||
|
||||
String _$deleteAccountControllerHash() =>
|
||||
r'293d3eacd0a4189e8849dd9f825a0a04c80cb0ff';
|
||||
|
||||
abstract class _$DeleteAccountController extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build();
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'delete_account_dialog_state_provider.g.dart';
|
||||
|
||||
class DeleteAccountDialogState {
|
||||
const DeleteAccountDialogState({this.step = 0, this.showPassword = false});
|
||||
|
||||
final int step;
|
||||
final bool showPassword;
|
||||
|
||||
DeleteAccountDialogState copyWith({int? step, bool? showPassword}) {
|
||||
return DeleteAccountDialogState(
|
||||
step: step ?? this.step,
|
||||
showPassword: showPassword ?? this.showPassword,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class DeleteAccountDialog extends _$DeleteAccountDialog {
|
||||
@override
|
||||
DeleteAccountDialogState build() => const DeleteAccountDialogState();
|
||||
|
||||
void advanceStep() => state = state.copyWith(step: state.step + 1);
|
||||
|
||||
void togglePasswordVisibility() =>
|
||||
state = state.copyWith(showPassword: !state.showPassword);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'delete_account_dialog_state_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(DeleteAccountDialog)
|
||||
const deleteAccountDialogProvider = DeleteAccountDialogProvider._();
|
||||
|
||||
final class DeleteAccountDialogProvider
|
||||
extends $NotifierProvider<DeleteAccountDialog, DeleteAccountDialogState> {
|
||||
const DeleteAccountDialogProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'deleteAccountDialogProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$deleteAccountDialogHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
DeleteAccountDialog create() => DeleteAccountDialog();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(DeleteAccountDialogState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<DeleteAccountDialogState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$deleteAccountDialogHash() =>
|
||||
r'f896ff3e6be1d19d44302299efe1fba1a2f4b547';
|
||||
|
||||
abstract class _$DeleteAccountDialog
|
||||
extends $Notifier<DeleteAccountDialogState> {
|
||||
DeleteAccountDialogState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref =
|
||||
this.ref as $Ref<DeleteAccountDialogState, DeleteAccountDialogState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<DeleteAccountDialogState, DeleteAccountDialogState>,
|
||||
DeleteAccountDialogState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:account/src/core/domain/repositories/users_repository.dart';
|
||||
import 'package:account/src/core/providers/users_repository_provider.dart';
|
||||
import 'package:account/src/features/delete_account/presentation/state/delete_account_view_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_shared/legacy_shared.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
final deleteAccountViewModelProvider =
|
||||
NotifierProvider.autoDispose<
|
||||
DeleteAccountViewModel,
|
||||
DeleteAccountViewState
|
||||
>(DeleteAccountViewModel.new);
|
||||
|
||||
class DeleteAccountViewModel extends Notifier<DeleteAccountViewState> {
|
||||
late final UsersRepository _usersRepository;
|
||||
late final SharedDevicesRepository _devicesRepository;
|
||||
late final SfTrackingRepository _tracking;
|
||||
late final TextEditingController passwordController;
|
||||
|
||||
@override
|
||||
DeleteAccountViewState build() {
|
||||
_usersRepository = ref.read(usersRepositoryProvider);
|
||||
_devicesRepository = ref.read(sharedDevicesRepositoryProvider);
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
|
||||
unawaited(_tracking.legacyAccountDeletionInitiated());
|
||||
|
||||
passwordController = TextEditingController();
|
||||
passwordController.addListener(_onPasswordChanged);
|
||||
|
||||
ref.onDispose(disposeListeners);
|
||||
|
||||
Future.microtask(() => load());
|
||||
|
||||
return const DeleteAccountViewState();
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
final user = await ref.read(userInfoProvider.future);
|
||||
setUser(user);
|
||||
|
||||
final devices = await _devicesRepository.getDevices();
|
||||
setDevices(devices);
|
||||
}
|
||||
|
||||
void setUser(UserEntity user) {
|
||||
state = state.copyWith(loggedUser: user);
|
||||
}
|
||||
|
||||
void setDevices(List<DeviceEntity> devices) {
|
||||
state = state.copyWith(
|
||||
deviceNames: devices.map((device) => device.carrierName!).toList(),
|
||||
deleteDevices: List<bool>.generate(devices.length, (_) => false),
|
||||
);
|
||||
}
|
||||
|
||||
void toggleDeleteDevice(int index) {
|
||||
List<bool> deleteDevices = state.deleteDevices;
|
||||
deleteDevices[index] = !deleteDevices[index];
|
||||
|
||||
state = state.copyWith(deleteDevices: deleteDevices);
|
||||
}
|
||||
|
||||
void _onPasswordChanged() {
|
||||
final value = passwordController.text;
|
||||
if (value == state.password) return;
|
||||
|
||||
state = state.copyWith(password: value, errorMessage: '');
|
||||
}
|
||||
|
||||
bool _validateForm() {
|
||||
if (state.password.trim().isEmpty) {
|
||||
state = state.copyWith(errorMessage: 'errorMessagePasswordIsEmpty');
|
||||
return false;
|
||||
}
|
||||
/*if (state.password.trim() != state.loggedUser.password.trim()) {
|
||||
state = state.copyWith(errorMessage: 'errorMessagePasswordsDontMatch');
|
||||
return false;
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
|
||||
void nextStep() {
|
||||
final step = state.confirmStep;
|
||||
|
||||
if (step == 0 && !_validateForm()) return;
|
||||
|
||||
state = state.copyWith(confirmStep: step + 1);
|
||||
}
|
||||
|
||||
void resetConfirmStep() {
|
||||
unawaited(_tracking.legacyAccountDeletionCancelled());
|
||||
state = state.copyWith(confirmStep: 0);
|
||||
}
|
||||
|
||||
Future<void> deleteAccount() async {
|
||||
if (state.isLoading) return;
|
||||
|
||||
try {
|
||||
state = state.copyWith(isLoading: true, isComplete: false);
|
||||
|
||||
unawaited(_tracking.legacyAccountDeletionConfirmed());
|
||||
|
||||
await _usersRepository.deleteUser(userId: state.loggedUser!.id);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
unawaited(_tracking.legacyAccountDeletionCompleted());
|
||||
|
||||
state = state.copyWith(isLoading: false, isComplete: true);
|
||||
} catch (e) {
|
||||
if (!ref.mounted) return;
|
||||
_finishWithError(message: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _finishWithError({required String message}) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
isComplete: false,
|
||||
errorMessage: message,
|
||||
);
|
||||
}
|
||||
|
||||
void disposeListeners() {
|
||||
passwordController.removeListener(_onPasswordChanged);
|
||||
passwordController.dispose();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user