Merge remote-tracking branch 'origin/develop' into components
# Conflicts: # apps/mobile_app/lib/navigation/app_router.dart # apps/mobile_app/pubspec.yaml # modules/auth/lib/src/device_sign_up/add_kid_screen.dart # modules/auth/lib/src/login/presentation/link_phone_screen.dart # modules/auth/lib/src/login/presentation/login_screen.dart # modules/auth/lib/src/login/presentation/phone_code_screen.dart # modules/auth/lib/src/onboarding/presentation/welcome_screen.dart # modules/auth/lib/src/recover_password/presentation/new_password_screen.dart # modules/auth/lib/src/sign_up/signup_screen.dart # modules/home/lib/src/presentation/deposit_screen.dart # modules/home/lib/src/presentation/home_screen.dart # modules/home/lib/src/presentation/wage_screen.dart # modules/notifications/lib/src/core/activity_list.dart # packages/design_system/lib/design_system.dart
This commit is contained in:
@@ -10,7 +10,7 @@ class AddKidScreen extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: Container(
|
||||
|
||||
@@ -63,7 +63,7 @@ class LinkPhoneScreen extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
PrimaryButton(
|
||||
onPressed: () => navigationContract.pushTo('/phone_code'),
|
||||
onPressed: () => navigationContract.pushTo(AppRoutes.phoneCode),
|
||||
text: "Siguiente",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
|
||||
@@ -4,16 +4,24 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
|
||||
class LoginScreen extends ConsumerWidget {
|
||||
class LoginScreen extends ConsumerStatefulWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const LoginScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends ConsumerState<LoginScreen>{
|
||||
bool passwordVisible = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
|
||||
bool passwordVisible = true;
|
||||
|
||||
final content = [
|
||||
@@ -51,7 +59,7 @@ class LoginScreen extends ConsumerWidget {
|
||||
child: CustomTextButton(
|
||||
text: "¿Has olvidado la contraseña?",
|
||||
onPressed: () =>
|
||||
navigationContract.pushTo('/recover_password'),
|
||||
widget.navigationContract.pushTo('/recover_password'),
|
||||
size: 16,
|
||||
)),
|
||||
],
|
||||
@@ -59,7 +67,7 @@ class LoginScreen extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
PrimaryButton(
|
||||
onPressed: () => navigationContract.goTo('/main/home'),
|
||||
onPressed: () => widget.navigationContract.goTo('/main/home'),
|
||||
text: "Iniciar sesión",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
@@ -73,7 +81,7 @@ class LoginScreen extends ConsumerWidget {
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
child: Text("o continúa con"),
|
||||
)
|
||||
@@ -116,7 +124,7 @@ class LoginScreen extends ConsumerWidget {
|
||||
style: TextStyle(fontSize: 18, letterSpacing: 0)
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => navigationContract.goTo('/signup'),
|
||||
onPressed: () => widget.navigationContract.goTo(AppRoutes.signup),
|
||||
child: Text(
|
||||
"Crear una ahora",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||
|
||||
@@ -4,90 +4,44 @@ import 'package:flutter_svg/svg.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
|
||||
class WelcomeScreen extends ConsumerStatefulWidget {
|
||||
class WelcomeScreen extends ConsumerWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const WelcomeScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => WelcomeScreenState(navigationContract: navigationContract);
|
||||
|
||||
}
|
||||
|
||||
class WelcomeScreenState extends ConsumerState{
|
||||
late int currentStep;
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
WelcomeScreenState({required this.navigationContract});
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentStep = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Center(
|
||||
child: Column(
|
||||
spacing: 48,
|
||||
children: [
|
||||
Spacer(),
|
||||
generateSteps()[currentStep],
|
||||
Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
StepIndicator(
|
||||
max: 3,
|
||||
current: currentStep+1,
|
||||
color: theme.getColorFor(ThemeCode.buttonSecondary)
|
||||
),
|
||||
generateButtons(theme, 3, currentStep+1)
|
||||
]
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: CarouselView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemExtent: double.infinity,
|
||||
itemSnapping: true,
|
||||
shrinkExtent: 400,
|
||||
children: generateSteps(),
|
||||
),
|
||||
Spacer()
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => navigationContract.goTo(AppRoutes.linkPhone),
|
||||
child: const Text('Continuar'),
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget generateButtons(ThemePort theme, int max, int step){
|
||||
if (step==max) {
|
||||
return PrimaryButton(
|
||||
onPressed: () => navigationContract.goTo('/link_phone'),
|
||||
text: "Continuar",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
width: 324,
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
PrimaryButton(
|
||||
onPressed: ()=>setState(() {
|
||||
currentStep++;
|
||||
}),
|
||||
text: "Siguiente",
|
||||
color: theme.getColorFor(ThemeCode.buttonSecondary),
|
||||
width: 324,
|
||||
),
|
||||
CustomTextButton(
|
||||
onPressed: ()=>navigationContract.goTo('/link_phone'),
|
||||
text: "Omitir",
|
||||
size: 18,
|
||||
weight: FontWeight.w500,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
void jumpToNext(BuildContext context) {
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (_) => LinkPhoneScreen()),
|
||||
// );
|
||||
return;
|
||||
}
|
||||
|
||||
List<Widget> generateSteps() {
|
||||
@@ -99,12 +53,12 @@ class WelcomeScreenState extends ConsumerState{
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
"Aprende a gestionar su dinero",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||
),
|
||||
Text(
|
||||
const Text(
|
||||
"Tu peque crea hábitos y se divierte mientras lo hace",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, letterSpacing: 0)
|
||||
@@ -120,12 +74,12 @@ class WelcomeScreenState extends ConsumerState{
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
"Tranquilidad en cada pago que hace",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||
),
|
||||
Text(
|
||||
const Text(
|
||||
"Supervisa sus gastos, fija límites y acompáñale en cada paso",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, letterSpacing: 0)
|
||||
@@ -141,12 +95,12 @@ class WelcomeScreenState extends ConsumerState{
|
||||
Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
"Pagos fáciles y seguros, en sus manos",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0)
|
||||
),
|
||||
Text(
|
||||
const Text(
|
||||
"Podrá pagar desde su reloj.\n Sin móvil ni efectivo",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, letterSpacing: 0)
|
||||
|
||||
@@ -14,26 +14,27 @@ class NewPasswordScreen extends ConsumerStatefulWidget {
|
||||
class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
bool passwordVisible = false;
|
||||
bool equalPasswords = false;
|
||||
String password = "";
|
||||
var securityChecks = {
|
||||
"min": false,
|
||||
"capital": false,
|
||||
"number": false,
|
||||
"special": false,
|
||||
String password = '';
|
||||
|
||||
Map<String, bool> securityChecks = {
|
||||
'min': false,
|
||||
'capital': false,
|
||||
'number': false,
|
||||
'special': false,
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
passwordVisible = false;
|
||||
equalPasswords = false;
|
||||
password = "";
|
||||
password = '';
|
||||
securityChecks = {
|
||||
"min": false,
|
||||
"capital": false,
|
||||
"number": false,
|
||||
"special": false,
|
||||
'min': false,
|
||||
'capital': false,
|
||||
'number': false,
|
||||
'special': false,
|
||||
};
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -44,16 +45,16 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
return Scaffold(
|
||||
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
body: Container(
|
||||
padding: EdgeInsets.all(24),
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Center(
|
||||
child: Column(
|
||||
spacing: 48,
|
||||
children: [
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
Column(
|
||||
spacing: 32,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
"Recuperar contraseña",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0),
|
||||
@@ -94,7 +95,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
? ThemeCode.buttonPrimary
|
||||
: ThemeCode.buttonSecondary),
|
||||
),
|
||||
Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)),
|
||||
const Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
@@ -106,7 +107,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
? ThemeCode.buttonPrimary
|
||||
: ThemeCode.buttonSecondary),
|
||||
),
|
||||
Text("Una mayúscula", style: TextStyle(fontSize: 14)),
|
||||
const Text("Una mayúscula", style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
@@ -118,7 +119,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
? ThemeCode.buttonPrimary
|
||||
: ThemeCode.buttonSecondary),
|
||||
),
|
||||
Text("Un número", style: TextStyle(fontSize: 14)),
|
||||
const Text("Un número", style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
@@ -130,7 +131,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
? ThemeCode.buttonPrimary
|
||||
: ThemeCode.buttonSecondary),
|
||||
),
|
||||
Text("Un carácter especial", style: TextStyle(fontSize: 14)),
|
||||
const Text("Un carácter especial", style: TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -142,7 +143,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
child: const Text(
|
||||
"Teléfono móvil",
|
||||
style: TextStyle(fontSize: 14, letterSpacing: 0),
|
||||
)
|
||||
@@ -172,7 +173,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
text: "Aceptar",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
Spacer(),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -182,13 +183,11 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
|
||||
//TODO: Extraer de la vista
|
||||
Map<String, bool> checkSecurity(String value) {
|
||||
Map<String, bool> checks = {};
|
||||
|
||||
checks["min"] = value.length >= 8;
|
||||
checks["capital"] = RegExp(r'[A-Z]').hasMatch(value);
|
||||
checks["number"] = RegExp(r'[0-9]').hasMatch(value);
|
||||
checks["special"] = RegExp(r'[^A-Za-z0-9]').hasMatch(value);
|
||||
|
||||
return checks;
|
||||
return {
|
||||
'min': value.length >= 8,
|
||||
'capital': RegExp(r'[A-Z]').hasMatch(value),
|
||||
'number': RegExp(r'[0-9]').hasMatch(value),
|
||||
'special': RegExp(r'[^A-Za-z0-9]').hasMatch(value),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,19 @@ class SignupScreen extends ConsumerStatefulWidget {
|
||||
super.key,
|
||||
required this.navigationContract
|
||||
});
|
||||
|
||||
ConsumerState<SignupScreen> createState() => SignupScreenState(navigationContract);
|
||||
@override
|
||||
ConsumerState<SignupScreen> createState() => _SignupScreenState();
|
||||
}
|
||||
|
||||
class SignupScreenState extends ConsumerState<SignupScreen> {
|
||||
late int currentStep;
|
||||
late bool acceptTerms;
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
SignupScreenState(this.navigationContract);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentStep = 0;
|
||||
acceptTerms = false;
|
||||
}
|
||||
class _SignupScreenState extends ConsumerState<SignupScreen> {
|
||||
int currentStep = 0;
|
||||
bool acceptTerms = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return getSteps()[currentStep];
|
||||
}
|
||||
|
||||
|
||||
List<Widget> getSteps() {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
@@ -118,7 +108,7 @@ class SignupScreenState extends ConsumerState<SignupScreen> {
|
||||
currentStep--;
|
||||
})},
|
||||
),
|
||||
AccountCreatedScreen(navigationContract: navigationContract, kidAccount: false)
|
||||
AccountCreatedScreen(navigationContract: widget.navigationContract, kidAccount: false)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ dependencies:
|
||||
path: ../../packages/design_system
|
||||
navigation:
|
||||
path: ../../packages/navigation
|
||||
sf_localizations:
|
||||
path: ../../packages/sf_localizations
|
||||
#dependencies go here
|
||||
flutter_svg: ^2.2.1
|
||||
get_it: ^9.0.5
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation
|
||||
# melos_managed_dependency_overrides: dashboard_shell,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
||||
dependency_overrides:
|
||||
dashboard_shell:
|
||||
path: ../dashboard_shell
|
||||
@@ -12,5 +12,9 @@ dependency_overrides:
|
||||
path: ../notifications
|
||||
profile:
|
||||
path: ../profile
|
||||
sf_localizations:
|
||||
path: ../../packages/sf_localizations
|
||||
sf_shared:
|
||||
path: ../../packages/sf_shared
|
||||
utils:
|
||||
path: ../../packages/utils
|
||||
|
||||
@@ -3,13 +3,12 @@ import 'package:dashboard_shell/src/presentation/main_shell_view_state.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
|
||||
final mainShellViewModelProvider =
|
||||
StateNotifierProvider.autoDispose<MainShellViewModel, MainShellViewState>(
|
||||
(ref) => MainShellViewModel(ref: ref),
|
||||
NotifierProvider.autoDispose<MainShellViewModel, MainShellViewState>(
|
||||
() => MainShellViewModel(),
|
||||
);
|
||||
|
||||
class DashboardScreen extends ConsumerWidget {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:dashboard_shell/src/presentation/main_shell_view_state.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.dart';
|
||||
|
||||
class MainShellViewModel extends StateNotifier<MainShellViewState> {
|
||||
MainShellViewModel({required this.ref}) : super(MainShellViewState());
|
||||
|
||||
final Ref ref;
|
||||
class MainShellViewModel extends Notifier<MainShellViewState> {
|
||||
@override
|
||||
MainShellViewState build() {
|
||||
return MainShellViewState();
|
||||
}
|
||||
|
||||
void onTabChanged(int index) {
|
||||
state = state.copyWith(selectedIndex: index);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation
|
||||
# melos_managed_dependency_overrides: auth,design_system,home,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
||||
dependency_overrides:
|
||||
auth:
|
||||
path: ../auth
|
||||
@@ -12,5 +12,9 @@ dependency_overrides:
|
||||
path: ../notifications
|
||||
profile:
|
||||
path: ../profile
|
||||
sf_localizations:
|
||||
path: ../../packages/sf_localizations
|
||||
sf_shared:
|
||||
path: ../../packages/sf_shared
|
||||
utils:
|
||||
path: ../../packages/utils
|
||||
|
||||
@@ -4,26 +4,31 @@ import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class DepositScreen extends ConsumerWidget {
|
||||
class DepositScreen extends ConsumerStatefulWidget {
|
||||
final Kid kid;
|
||||
|
||||
DepositScreen({super.key, required this.kid});
|
||||
const DepositScreen({super.key, required this.kid});
|
||||
|
||||
String reason = "other";
|
||||
@override
|
||||
ConsumerState<DepositScreen> createState() => _DepositScreenState();
|
||||
}
|
||||
|
||||
class _DepositScreenState extends ConsumerState<DepositScreen> {
|
||||
String reason = 'other';
|
||||
bool program = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return WalletManagementLayout(
|
||||
kid: kid,
|
||||
kid: widget.kid,
|
||||
footer: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
@@ -33,7 +38,7 @@ class DepositScreen extends ConsumerWidget {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text("Cancelar"),
|
||||
child: const Text('Cancelar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -42,14 +47,14 @@ class DepositScreen extends ConsumerWidget {
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
"Ingresar dinero en el wallet",
|
||||
const Text(
|
||||
'Ingresar dinero en el wallet',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
CustomTextField(
|
||||
@@ -57,72 +62,73 @@ class DepositScreen extends ConsumerWidget {
|
||||
label: "Cantidad",
|
||||
hint: "0€",
|
||||
),
|
||||
Align(
|
||||
const Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text("Saldo total disponible después: 30 €"),
|
||||
child: Text('Saldo total disponible después: 30 €'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
"Motivo",
|
||||
const Text(
|
||||
'Motivo',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
Text("Este dato aparecerá en el reloj del peque"),
|
||||
const Text('Este dato aparecerá en el reloj del peque'),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Paga semanal'),
|
||||
title: const Text('Paga semanal'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: reason == "weekly",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// reason = "weekly";
|
||||
// });
|
||||
value: reason == 'weekly',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
reason = 'weekly';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Objetivo semanal cumplido'),
|
||||
title: const Text('Objetivo semanal cumplido'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: reason == "goal",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// reason = "goal";
|
||||
// });
|
||||
value: reason == 'goal',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
reason = 'goal';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Gastos extraordinarios'),
|
||||
title: const Text('Gastos extraordinarios'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: reason == "extraordinary",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// reason = "extraordinary";
|
||||
// });
|
||||
value: reason == 'extraordinary',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
reason = 'extraordinary';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Otro'),
|
||||
title: const Text('Otro'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: reason == "other",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// reason = "other";
|
||||
// });
|
||||
value: reason == 'other',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
reason = 'other';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
@@ -132,48 +138,49 @@ class DepositScreen extends ConsumerWidget {
|
||||
label: "Escribir mensaje a ${kid.name} del motivo del ingreso",
|
||||
hint: "Escribe tu mensaje",
|
||||
),
|
||||
Align(
|
||||
const Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text("Máximo 150 caracteres"),
|
||||
child: Text('Máximo 150 caracteres'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
"Cuándo se envía el dinero",
|
||||
const Text(
|
||||
'Cuándo se envía el dinero',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
Text("Este dato aparecerá en el reloj del peque"),
|
||||
const Text('Este dato aparecerá en el reloj del peque'),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Ahora'),
|
||||
title: const Text('Ahora'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: program == false,
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// program = false;
|
||||
// });
|
||||
value: !program,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
program = false;
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Programar'),
|
||||
title: const Text('Programar'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: program == true,
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// program = true;
|
||||
// });
|
||||
value: program,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
program = true;
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
|
||||
@@ -4,29 +4,35 @@ import 'package:home/src/presentation/wallet_management_layout.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class WageScreen extends ConsumerWidget {
|
||||
class WageScreen extends ConsumerStatefulWidget {
|
||||
final Kid kid;
|
||||
|
||||
WageScreen({super.key, required this.kid});
|
||||
const WageScreen({super.key, required this.kid});
|
||||
|
||||
String frequence = "weekly";
|
||||
var conditions = {
|
||||
"weeklyLimits": false,
|
||||
"incidences": false,
|
||||
"holidays": false,
|
||||
@override
|
||||
ConsumerState<WageScreen> createState() => _WageScreenState();
|
||||
}
|
||||
|
||||
class _WageScreenState extends ConsumerState<WageScreen> {
|
||||
String frequence = 'weekly';
|
||||
|
||||
final Map<String, bool> conditions = {
|
||||
'weeklyLimits': false,
|
||||
'incidences': false,
|
||||
'holidays': false,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
return WalletManagementLayout(
|
||||
kid: kid,
|
||||
kid: widget.kid,
|
||||
footer: Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.only(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
@@ -39,7 +45,7 @@ class WageScreen extends ConsumerWidget {
|
||||
text: "Activar paga automática",
|
||||
color: theme.getColorFor(ThemeCode.buttonPrimary)
|
||||
),
|
||||
TextButton(onPressed: () => {}, child: Text("Cancelar")),
|
||||
TextButton(onPressed: () {}, child: const Text('Cancelar')),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -47,14 +53,14 @@ class WageScreen extends ConsumerWidget {
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Align(alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
child: const Text(
|
||||
"Paga automática",
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
|
||||
),
|
||||
@@ -64,61 +70,62 @@ class WageScreen extends ConsumerWidget {
|
||||
label: "Cantidad",
|
||||
hint: "0€",
|
||||
),
|
||||
Text("Saldo total disponible después: 30 €"),
|
||||
const Text('Saldo total disponible después: 30 €'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Align(alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
child: const Text(
|
||||
"Frecuencia",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
)
|
||||
),
|
||||
Align(alignment: Alignment.topLeft,
|
||||
child: Text("Cuándo se envía el dinero"),
|
||||
child: const Text("Cuándo se envía el dinero"),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Semanal'),
|
||||
title: const Text('Semanal'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: frequence == "weekly",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// frequence = "weekly";
|
||||
// });
|
||||
value: frequence == 'weekly',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
frequence = 'weekly';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Cada dos semanas'),
|
||||
title: const Text('Cada dos semanas'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: frequence == "biweekly",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// frequence = "biweekly";
|
||||
// });
|
||||
value: frequence == 'biweekly',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
frequence = 'biweekly';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Mensual'),
|
||||
title: const Text('Mensual'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: frequence == "monthly",
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// frequence = "monthly";
|
||||
// });
|
||||
value: frequence == 'monthly',
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
frequence = 'monthly';
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
@@ -150,64 +157,67 @@ class WageScreen extends ConsumerWidget {
|
||||
"Escribir mensaje a ${kid.name} del motivo del ingreso",
|
||||
hint: "Escribe tu mensaje",
|
||||
),
|
||||
Align(
|
||||
const Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text("Máximo 150 caracteres"),
|
||||
child: Text('Máximo 150 caracteres'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Align(alignment: Alignment.topLeft,
|
||||
const Align(alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
"Condiciones",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
),
|
||||
Align(alignment: Alignment.topLeft,
|
||||
const Align(alignment: Alignment.topLeft,
|
||||
child: Text("Este dato aparecerá en el reloj del peque"),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Sólo si cumple límites semanales'),
|
||||
title: const Text('Sólo si cumple límites semanales'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: conditions["weeklyLimits"],
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// conditions["weeklyLimits"] = !conditions["weeklyLimits"]!;
|
||||
// });
|
||||
value: conditions['weeklyLimits'],
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
conditions['weeklyLimits'] =
|
||||
!(conditions['weeklyLimits'] ?? false);
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Sólo si no ha tenido incidencias'),
|
||||
title: const Text('Sólo si no ha tenido incidencias'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: conditions["incidences"],
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// conditions["incidences"] = !conditions["incidences"]!;
|
||||
// });
|
||||
value: conditions['incidences'],
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
conditions['incidences'] =
|
||||
!(conditions['incidences'] ?? false);
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text('Pausar durante vacaciones'),
|
||||
title: const Text('Pausar durante vacaciones'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: conditions["holidays"],
|
||||
onChanged: (value) {
|
||||
// setState(() {
|
||||
// conditions["holidays"] = !conditions["holidays"]!;
|
||||
// });
|
||||
value: conditions['holidays'],
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
conditions['holidays'] = !(conditions['holidays'] ?? false);
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation
|
||||
# melos_managed_dependency_overrides: auth,dashboard_shell,design_system,notifications,profile,sf_shared,navigation,utils,sf_localizations
|
||||
dependency_overrides:
|
||||
auth:
|
||||
path: ../auth
|
||||
@@ -12,5 +12,9 @@ dependency_overrides:
|
||||
path: ../notifications
|
||||
profile:
|
||||
path: ../profile
|
||||
sf_localizations:
|
||||
path: ../../packages/sf_localizations
|
||||
sf_shared:
|
||||
path: ../../packages/sf_shared
|
||||
utils:
|
||||
path: ../../packages/utils
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ActivityList extends ConsumerStatefulWidget {
|
||||
final List activity;
|
||||
bool edit = false;
|
||||
final bool edit;
|
||||
|
||||
ActivityList({super.key, required this.activity, required this.edit});
|
||||
const ActivityList({super.key, required this.activity, this.edit = false});
|
||||
|
||||
@override
|
||||
ConsumerState<ActivityList> createState() => ActivityListState();
|
||||
@@ -17,28 +17,47 @@ class ActivityListState extends ConsumerState<ActivityList> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
values = List<bool>.generate(widget.activity.length, (_) => false);
|
||||
super.initState();
|
||||
values = List<bool>.generate(widget.activity.length, (_) => false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ref.watch(themePortProvider);
|
||||
|
||||
final colors = [Colors.cyan, Colors.pinkAccent, Colors.deepOrangeAccent, Colors.red];
|
||||
final icons = {"wage": Icons.wallet, "goal": Icons.emoji_events_outlined, "lock": Icons.lock_outline, "reload": Icons.attach_money_outlined};
|
||||
final titles = {"wage": "Entrega de paga", "goal": "¡Objetivo cumplido!", "lock": "Bloqueo de pago", "reload": "Recarga familiar"};
|
||||
const colors = [
|
||||
Colors.cyan,
|
||||
Colors.pinkAccent,
|
||||
Colors.deepOrangeAccent,
|
||||
Colors.red,
|
||||
];
|
||||
|
||||
const icons = {
|
||||
"wage": Icons.wallet,
|
||||
"goal": Icons.emoji_events_outlined,
|
||||
"lock": Icons.lock_outline,
|
||||
"reload": Icons.attach_money_outlined,
|
||||
};
|
||||
|
||||
const titles = {
|
||||
"wage": "Entrega de paga",
|
||||
"goal": "¡Objetivo cumplido!",
|
||||
"lock": "Bloqueo de pago",
|
||||
"reload": "Recarga familiar",
|
||||
};
|
||||
|
||||
return Column(
|
||||
spacing: 32,
|
||||
children: List<Widget>.generate(widget.activity.length, (int index) {
|
||||
var logItem = Container(
|
||||
padding: EdgeInsets.all(24),
|
||||
final color = colors[index % colors.length];
|
||||
final type = widget.activity[index]["type"] as String;
|
||||
|
||||
final logItem = Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.getColorFor(ThemeCode.backgroundPrimary),
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
border: BoxBorder.fromLTRB(left: BorderSide(
|
||||
color: colors[index % colors.length], width: 8))
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
border: Border(left: BorderSide(color: color, width: 8)),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
@@ -46,17 +65,20 @@ class ActivityListState extends ConsumerState<ActivityList> {
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(icons[widget.activity[index]["type"]],
|
||||
color: colors[index % colors.length],
|
||||
size: 20
|
||||
Icon(icons[type], color: color),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
titles[type]!,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Expanded(child: Text(titles[widget.activity[index]["type"]]!,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18, letterSpacing: 0)
|
||||
)),
|
||||
Text("14/01/2005", style: TextStyle(fontSize: 14, letterSpacing: 0))
|
||||
]
|
||||
const Spacer(),
|
||||
const Text("14/01/2005"),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
const Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text("Ana ya tiene su paga de 5€ en el reloj", style: TextStyle(fontSize: 14, letterSpacing: 0)),
|
||||
)
|
||||
@@ -70,10 +92,11 @@ class ActivityListState extends ConsumerState<ActivityList> {
|
||||
children: [
|
||||
Checkbox(
|
||||
value: values[index],
|
||||
onChanged: (value) => {
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
values[index] = !values[index];
|
||||
})},
|
||||
});
|
||||
},
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
semanticLabel: "Eliminar"
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# melos_managed_dependency_overrides: design_system,sf_shared
|
||||
# melos_managed_dependency_overrides: design_system,sf_shared,utils
|
||||
dependency_overrides:
|
||||
design_system:
|
||||
path: ../../packages/design_system
|
||||
sf_shared:
|
||||
path: ../../packages/sf_shared
|
||||
utils:
|
||||
path: ../../packages/utils
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# melos_managed_dependency_overrides: design_system,notifications,sf_shared
|
||||
# melos_managed_dependency_overrides: design_system,notifications,sf_shared,utils
|
||||
dependency_overrides:
|
||||
design_system:
|
||||
path: ../../packages/design_system
|
||||
@@ -6,3 +6,5 @@ dependency_overrides:
|
||||
path: ../notifications
|
||||
sf_shared:
|
||||
path: ../../packages/sf_shared
|
||||
utils:
|
||||
path: ../../packages/utils
|
||||
|
||||
Reference in New Issue
Block a user