save before flutter upgrade
This commit is contained in:
12
lib/functions/check_password.dart
Normal file
12
lib/functions/check_password.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
String? checkPassword(String password) {
|
||||
if (password.length < 8) {
|
||||
return 'La contraseña debe tener al menos 8 caracteres';
|
||||
}
|
||||
final RegExp regex =
|
||||
RegExp(r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$');
|
||||
final RegExpMatch? match = regex.firstMatch(password);
|
||||
if (match == null) {
|
||||
return 'La contraseña debe tener al menos un número, una letra mayúscula, una letra minúscula, un caracter especial y no debe tener números ni letras consecutivas';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
104
lib/functions/date_format.dart
Normal file
104
lib/functions/date_format.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String dateFormat(
|
||||
dynamic x, // Acepta DateTime o String
|
||||
[
|
||||
bool includeTime = false,
|
||||
bool includeDayName = true,
|
||||
bool includeTimeAgo = false,
|
||||
String language = 'es_MX',
|
||||
]) {
|
||||
DateTime? date;
|
||||
|
||||
// Verifica si es una cadena de fecha y conviértela a DateTime
|
||||
if (x is String) {
|
||||
date = DateTime.tryParse(x);
|
||||
} else if (x is DateTime) {
|
||||
date = x;
|
||||
}
|
||||
|
||||
if (date == null) return '-';
|
||||
|
||||
// Convierte a hora local
|
||||
date = date.toLocal();
|
||||
|
||||
String formattedDate;
|
||||
|
||||
// Formatea la fecha
|
||||
if (!includeTime) {
|
||||
if (includeDayName) {
|
||||
|
||||
formattedDate = DateFormat('EEE. dd/MMM/yyyy', language).format(date);
|
||||
}
|
||||
else{
|
||||
formattedDate = DateFormat('dd/MMM/yyyy', language).format(date);
|
||||
}
|
||||
} else {
|
||||
formattedDate = DateFormat('EEE. dd/MMM/yyyy HH:mm', language).format(date);
|
||||
}
|
||||
|
||||
// Capitaliza la primera letra del día
|
||||
List<String> parts = formattedDate.split(' ');
|
||||
|
||||
if (parts.isNotEmpty) {
|
||||
parts[0] = parts[0][0].toUpperCase() + parts[0].substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
// Capitaliza la primera letra del mes
|
||||
if (parts.length >= 2) {
|
||||
String datePart = parts[1]; // "dd/MMM/yyyy" o "dd/MMM/yyyy HH:mm"
|
||||
List<String> dateComponents = datePart.split('/');
|
||||
|
||||
if (dateComponents.length >= 2) {
|
||||
// Capitaliza el mes
|
||||
dateComponents[1] = dateComponents[1][0].toUpperCase() +
|
||||
dateComponents[1].substring(1).toLowerCase();
|
||||
parts[1] = dateComponents.join('/');
|
||||
}
|
||||
}
|
||||
|
||||
formattedDate = parts.join(' ');
|
||||
|
||||
// Manejo de includeTimeAgo
|
||||
if (includeTimeAgo) {
|
||||
final Duration difference = DateTime.now().difference(date);
|
||||
String timeAgo = '';
|
||||
|
||||
if (difference.isNegative) {
|
||||
timeAgo = '\n(hace 0 segundos)';
|
||||
} else {
|
||||
if (difference.inSeconds < 60) {
|
||||
timeAgo = '\n(hace ${difference.inSeconds} segundos)';
|
||||
} else if (difference.inMinutes < 60) {
|
||||
timeAgo = '\n(hace ${difference.inMinutes} minutos)';
|
||||
} else if (difference.inHours < 48) {
|
||||
timeAgo = '\n(hace ${difference.inHours} horas)';
|
||||
} else if (difference.inDays <= 30) {
|
||||
timeAgo = '\n(hace ${difference.inDays} días)';
|
||||
} else if (difference.inDays > 30) {
|
||||
timeAgo = '\n(hace ${difference.inDays ~/ 30} meses)';
|
||||
}
|
||||
}
|
||||
|
||||
formattedDate = '$formattedDate $timeAgo';
|
||||
}
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
// Definir una lista de nombres de los días de la semana
|
||||
List<String> daysOfWeek = [
|
||||
'Lunes',
|
||||
'Martes',
|
||||
'Miércoles',
|
||||
'Jueves',
|
||||
'Viernes',
|
||||
'Sábado',
|
||||
'Domingo'
|
||||
];
|
||||
|
||||
// Obtener el nombre del día de la semana
|
||||
String getDayName(DateTime now) {
|
||||
String dayName = daysOfWeek[now.weekday - 1];
|
||||
return dayName;
|
||||
}
|
||||
9
lib/functions/date_time_format.dart
Normal file
9
lib/functions/date_time_format.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String dateTimeFormat(DateTime? date) {
|
||||
if (date == null) {
|
||||
return '-';
|
||||
}
|
||||
final formato = DateFormat('MM-dd-yyyy HH:mm:ss');
|
||||
return formato.format(date);
|
||||
}
|
||||
11
lib/functions/day_month_format.dart
Normal file
11
lib/functions/day_month_format.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String dayMothFormat(DateTime fecha) {
|
||||
String dia = DateFormat('d').format(fecha);
|
||||
|
||||
String mes = DateFormat('MMM', 'es').format(fecha);
|
||||
mes = mes[0].toUpperCase() + mes.substring(1);
|
||||
mes = mes.replaceAll('.', '');
|
||||
|
||||
return '$dia $mes';
|
||||
}
|
||||
8
lib/functions/double_extensions.dart
Normal file
8
lib/functions/double_extensions.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
extension Precision on double {
|
||||
double toPrecision(int fractionDigits) {
|
||||
var mod = pow(10, fractionDigits.toDouble()).toDouble();
|
||||
return ((this * mod).round().toDouble() / mod);
|
||||
}
|
||||
}
|
||||
13
lib/functions/extract_number_from_text.dart
Normal file
13
lib/functions/extract_number_from_text.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
int? extractNumberFromText(String text) {
|
||||
// Expresión regular para encontrar el primer número en el texto
|
||||
RegExp regExp = RegExp(r'\d+');
|
||||
Match? match = regExp.firstMatch(text);
|
||||
|
||||
// Si se encuentra un número, devolverlo como un entero
|
||||
if (match != null) {
|
||||
return int.tryParse(match.group(0) ?? '');
|
||||
}
|
||||
|
||||
// Si no se encuentra ningún número, devolver null
|
||||
return null;
|
||||
}
|
||||
4
lib/functions/form_textfield_width.dart
Normal file
4
lib/functions/form_textfield_width.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
double formTextFieldWidth(double anchoPantalla, double anchoMin, double anchoMax, double valorMin, double valorMax) {
|
||||
// Fórmula de interpolación lineal para calcular tamaños adaptativos.
|
||||
return valorMin + (valorMax - valorMin) * ((anchoPantalla - anchoMin) / (anchoMax - anchoMin));
|
||||
}
|
||||
12
lib/functions/money_format.dart
Normal file
12
lib/functions/money_format.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
String moneyFormat(double x) {
|
||||
List<String> parts = x.toString().split('.');
|
||||
RegExp re = RegExp(r'\B(?=(\d{3})+(?!\d))');
|
||||
|
||||
parts[0] = parts[0].replaceAll(re, ',');
|
||||
if (parts.length == 1) {
|
||||
parts.add('00');
|
||||
} else {
|
||||
parts[1] = parts[1].padRight(2, '0').substring(0, 2);
|
||||
}
|
||||
return parts.join('.');
|
||||
}
|
||||
12
lib/functions/money_format_3_decimals.dart
Normal file
12
lib/functions/money_format_3_decimals.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
String moneyFormat3Decimals(double x) {
|
||||
List<String> parts = x.toString().split('.');
|
||||
RegExp re = RegExp(r'\B(?=(\d{3})+(?!\d))');
|
||||
|
||||
parts[0] = parts[0].replaceAll(re, ',');
|
||||
if (parts.length == 1) {
|
||||
parts.add('00');
|
||||
} else {
|
||||
parts[1] = parts[1].padRight(3, '0').substring(0, 3);
|
||||
}
|
||||
return parts.join('.');
|
||||
}
|
||||
11
lib/functions/month_name.dart
Normal file
11
lib/functions/month_name.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String monthName(int mes) {
|
||||
DateTime dateTime = DateTime(DateTime.now().year, mes);
|
||||
|
||||
String value = DateFormat('MMMM', 'es').format(dateTime);
|
||||
|
||||
value = value[0].toUpperCase() + value.substring(1);
|
||||
|
||||
return value;
|
||||
}
|
||||
15
lib/functions/no_transition_route.dart
Normal file
15
lib/functions/no_transition_route.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
GoRoute noTransitionRoute(String path, String name, Widget Function(BuildContext, GoRouterState) builder, {List<GoRoute>? subroutes}) {
|
||||
return GoRoute(
|
||||
path: path,
|
||||
name: name,
|
||||
pageBuilder: (context, state) {
|
||||
return NoTransitionPage(
|
||||
child: builder(context, state),
|
||||
);
|
||||
},
|
||||
routes: subroutes ?? [],
|
||||
);
|
||||
}
|
||||
4
lib/functions/phone_format.dart
Normal file
4
lib/functions/phone_format.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
String formatPhone(String phone) {
|
||||
if (phone.length != 10) return phone;
|
||||
return '(${phone.substring(0, 3)}) ${phone.substring(3, 6)} ${phone.substring(6, 8)} ${phone.substring(8, 10)}';
|
||||
}
|
||||
20
lib/functions/tokens.dart
Normal file
20
lib/functions/tokens.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
|
||||
Token? parseToken(String token) {
|
||||
try {
|
||||
// Verify a token
|
||||
final jwt = JWT.verify(token, SecretKey('secret'));
|
||||
return Token.fromJson(json.encode(jwt.payload), token);
|
||||
} on JWTExpiredException {
|
||||
log('JWT expirada');
|
||||
} on JWTException catch (ex) {
|
||||
log('Error en checkToken - $ex');
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
34
lib/helpers/color_extension.dart
Normal file
34
lib/helpers/color_extension.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:ui';
|
||||
|
||||
extension ColorExtension on Color {
|
||||
/// Convert the color to a darken color based on the [percent]
|
||||
Color darken([int percent = 40]) {
|
||||
assert(1 <= percent && percent <= 100);
|
||||
final value = 1 - percent / 100;
|
||||
return Color.fromARGB(
|
||||
alpha,
|
||||
(red * value).round(),
|
||||
(green * value).round(),
|
||||
(blue * value).round(),
|
||||
);
|
||||
}
|
||||
|
||||
Color lighten([int percent = 40]) {
|
||||
assert(1 <= percent && percent <= 100);
|
||||
final value = percent / 100;
|
||||
return Color.fromARGB(
|
||||
alpha,
|
||||
(red + ((255 - red) * value)).round(),
|
||||
(green + ((255 - green) * value)).round(),
|
||||
(blue + ((255 - blue) * value)).round(),
|
||||
);
|
||||
}
|
||||
|
||||
Color avg(Color other) {
|
||||
final red = (this.red + other.red) ~/ 2;
|
||||
final green = (this.green + other.green) ~/ 2;
|
||||
final blue = (this.blue + other.blue) ~/ 2;
|
||||
final alpha = (this.alpha + other.alpha) ~/ 2;
|
||||
return Color.fromARGB(alpha, red, green, blue);
|
||||
}
|
||||
}
|
||||
30
lib/helpers/constants.dart
Normal file
30
lib/helpers/constants.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
//////// DEV CBLUNA ////////
|
||||
const String supabaseUrl = 'https://cbl-supabase.virtalus.cbluna-dev.com';
|
||||
const String anonKey =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzE1MjM4MDAwLAogICJleHAiOiAxODczMDA0NDAwCn0.qKqYn2vjtHqKqyt1FAghuIjvNsyr9b1ElpVfvJg6zJ4';
|
||||
const String storageBooksCover =
|
||||
'https://cbl-supabase.virtalus.cbluna-dev.com/storage/v1/object/public/lectores_urb/books_cover/';
|
||||
const String storageBooks = 'lectores_urb/books';
|
||||
|
||||
const redirectUrl = '$supabaseUrl/change-pass/change-password/token';
|
||||
|
||||
const String apiGatewayUrl = 'https://cbl.virtalus.cbluna-dev.com/uapi/lu/api';
|
||||
const String n8nUrl = 'https://u-n8n.virtalus.cbluna-dev.com/webhook';
|
||||
const bearerApiGateway = "Basic YWlyZmxvdzpjYiF1bmEyMDIz";
|
||||
|
||||
const int organizationId = 10;
|
||||
const String lectoresUrl = 'https://lectoresurbanos.com/';
|
||||
|
||||
const themeId = String.fromEnvironment('themeId', defaultValue: '2');
|
||||
const int mobileSize = 800;
|
||||
|
||||
//GoogleMaps
|
||||
const apiKey = '-plDP_dR7XAGxBSiHgTFyxkxNdjFFHqjQK9ge8b92CE';
|
||||
|
||||
//Stripe
|
||||
const stripeKey =
|
||||
'sk_test_51QB3lZRxye2e7dlWyeWcHWmgsJ9kEbIz4lrgJtUIKuC7WEB0HM0njk6Mcgq1q1H9GTs7fByRZdvxHaSAGhzcBqYF00fLxZU9E9';
|
||||
const authorizationKey = 'Bearer $stripeKey';
|
||||
|
||||
const urlStripe = 'https://api.stripe.com';
|
||||
const idPricePlanRentaQR = 'price_1QPaDnRxye2e7dlWORf4GoHS';
|
||||
325
lib/helpers/globals.dart
Normal file
325
lib/helpers/globals.dart
Normal file
@@ -0,0 +1,325 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:pluto_grid/pluto_grid.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' hide User;
|
||||
|
||||
import 'package:nethive_neo/helpers/supabase/queries.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
final GlobalKey<ScaffoldMessengerState> snackbarKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
late SupabaseClient supabaseLU;
|
||||
|
||||
late final SharedPreferences prefs;
|
||||
late Configuration? theme;
|
||||
|
||||
User? currentUser;
|
||||
|
||||
Future<void> initGlobals() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
currentUser = await SupabaseQueries.getCurrentUserData();
|
||||
/* //Temas de la aplicacion
|
||||
if (currentUser == null) {
|
||||
Configuration? conf = await SupabaseQueries.getDefaultTheme();
|
||||
AppTheme.initConfiguration(conf);
|
||||
} else {
|
||||
theme = await SupabaseQueries.getUserTheme();
|
||||
AppTheme.initConfiguration(theme);
|
||||
}
|
||||
*/
|
||||
if (currentUser == null) return;
|
||||
}
|
||||
|
||||
PlutoGridScrollbarConfig plutoGridScrollbarConfig(BuildContext context) {
|
||||
return PlutoGridScrollbarConfig(
|
||||
isAlwaysShown: true,
|
||||
scrollbarThickness: 5,
|
||||
hoverWidth: 20,
|
||||
scrollBarColor: AppTheme.of(context).primaryColor,
|
||||
);
|
||||
}
|
||||
|
||||
PlutoGridStyleConfig plutoGridStyleConfig(BuildContext context,
|
||||
{double rowHeight = 50}) {
|
||||
return AppTheme.themeMode == ThemeMode.light
|
||||
? PlutoGridStyleConfig(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: rowHeight,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
)
|
||||
: PlutoGridStyleConfig.dark(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: rowHeight,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
);
|
||||
}
|
||||
|
||||
PlutoGridStyleConfig plutoGridBigStyleConfig(BuildContext context) {
|
||||
return AppTheme.themeMode == ThemeMode.light
|
||||
? PlutoGridStyleConfig(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).hintText,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: Colors.transparent,
|
||||
rowHeight: 50,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
columnHeight: 100,
|
||||
gridBorderRadius: BorderRadius.circular(16),
|
||||
)
|
||||
: PlutoGridStyleConfig.dark(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: 50,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
columnHeight: 100,
|
||||
gridBorderRadius: BorderRadius.circular(16),
|
||||
);
|
||||
}
|
||||
|
||||
PlutoGridStyleConfig plutoGridDashboardStyleConfig(BuildContext context) {
|
||||
return AppTheme.themeMode == ThemeMode.light
|
||||
? PlutoGridStyleConfig(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).hintText,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: 50,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
)
|
||||
: PlutoGridStyleConfig.dark(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: false,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: 50,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
);
|
||||
}
|
||||
|
||||
double rowHeight = 60;
|
||||
|
||||
PlutoGridStyleConfig plutoGridPopUpStyleConfig(BuildContext context) {
|
||||
return AppTheme.themeMode == ThemeMode.light
|
||||
? PlutoGridStyleConfig(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryBackground,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
//
|
||||
enableColumnBorderVertical: false,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
borderColor: Colors.transparent,
|
||||
//
|
||||
rowHeight: 40,
|
||||
rowColor: Colors.transparent,
|
||||
cellTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: false,
|
||||
checkedColor: Colors.transparent,
|
||||
enableRowColorAnimation: false,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
//
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
)
|
||||
: PlutoGridStyleConfig.dark(
|
||||
menuBackgroundColor: Colors.transparent,
|
||||
//
|
||||
enableColumnBorderVertical: false,
|
||||
columnTextStyle: AppTheme.of(context).copyRightText,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
borderColor: Colors.transparent,
|
||||
//
|
||||
rowHeight: 40,
|
||||
rowColor: Colors.transparent,
|
||||
cellTextStyle: AppTheme.of(context).copyRightText,
|
||||
enableColumnBorderHorizontal: false,
|
||||
enableCellBorderVertical: false,
|
||||
enableCellBorderHorizontal: false,
|
||||
checkedColor: Colors.transparent,
|
||||
enableRowColorAnimation: false,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
//
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
);
|
||||
}
|
||||
|
||||
PlutoGridStyleConfig plutoGridStyleConfigContentManager(BuildContext context,
|
||||
{double rowHeight = 50}) {
|
||||
return AppTheme.themeMode == ThemeMode.light
|
||||
? PlutoGridStyleConfig(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: true,
|
||||
enableColumnBorderHorizontal: true,
|
||||
enableCellBorderVertical: true,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnHeight: 100,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: rowHeight,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
gridBorderRadius: BorderRadius.circular(16),
|
||||
)
|
||||
: PlutoGridStyleConfig.dark(
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryColor,
|
||||
gridPopupBorderRadius: BorderRadius.circular(16),
|
||||
enableColumnBorderVertical: true,
|
||||
enableColumnBorderHorizontal: true,
|
||||
enableCellBorderVertical: true,
|
||||
enableCellBorderHorizontal: true,
|
||||
columnHeight: 100,
|
||||
columnTextStyle: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: 'Quicksand',
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
cellTextStyle: AppTheme.of(context).bodyText3,
|
||||
iconColor: AppTheme.of(context).tertiaryColor,
|
||||
rowColor: Colors.transparent,
|
||||
borderColor: const Color(0xFFF1F4FA),
|
||||
rowHeight: rowHeight,
|
||||
checkedColor: AppTheme.themeMode == ThemeMode.light
|
||||
? AppTheme.of(context).secondaryColor
|
||||
: const Color(0XFF4B4B4B),
|
||||
enableRowColorAnimation: true,
|
||||
gridBackgroundColor: Colors.transparent,
|
||||
gridBorderColor: Colors.transparent,
|
||||
activatedColor: AppTheme.of(context).primaryBackground,
|
||||
activatedBorderColor: AppTheme.of(context).tertiaryColor,
|
||||
);
|
||||
}
|
||||
12
lib/helpers/scroll_behavior.dart
Normal file
12
lib/helpers/scroll_behavior.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
// Create Custom Scroll Class for web Drap Behaviour:
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MyCustomScrollBehavior extends MaterialScrollBehavior {
|
||||
// Override behavior methods and getters like dragDevices
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
87
lib/helpers/supabase/queries.dart
Normal file
87
lib/helpers/supabase/queries.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' hide User;
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
|
||||
class SupabaseQueries {
|
||||
static Future<User?> getCurrentUserData() async {
|
||||
try {
|
||||
final user = supabase.auth.currentUser;
|
||||
if (user == null) return null;
|
||||
|
||||
final PostgrestFilterBuilder query =
|
||||
supabase.from('users').select().eq('user_profile_id', user.id);
|
||||
|
||||
final res = await query;
|
||||
|
||||
final userProfile = res[0];
|
||||
userProfile['id'] = user.id;
|
||||
userProfile['email'] = user.email!;
|
||||
|
||||
final usuario = User.fromMap(userProfile);
|
||||
|
||||
return usuario;
|
||||
} catch (e) {
|
||||
log('Error en getCurrentUserData() - $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Configuration?> getDefaultTheme() async {
|
||||
try {
|
||||
final res = await supabase.from('theme').select().eq('id', 1);
|
||||
return Configuration.fromJson(jsonEncode(res[0]));
|
||||
} catch (e) {
|
||||
log('Error en getDefaultTheme() - $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Configuration?> getUserTheme() async {
|
||||
try {
|
||||
if (currentUser == null) return null;
|
||||
final res = await supabase.from('users').select('config').eq(
|
||||
'sequential_id',
|
||||
currentUser!
|
||||
.sequentialId); //final res = await supabase.from('theme').select().eq('id', 2);
|
||||
return Configuration.fromJson(jsonEncode(res[0]));
|
||||
} catch (e) {
|
||||
log('Error en getUserTheme() - $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> tokenChangePassword(String id, String newPassword) async {
|
||||
try {
|
||||
final res = await supabase.rpc('token_change_password', params: {
|
||||
'user_id': id,
|
||||
'new_password': newPassword,
|
||||
});
|
||||
|
||||
if (res['data'] == true) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
log('Error en tokenChangePassword() - $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<bool> saveToken(
|
||||
String userId,
|
||||
String tokenType,
|
||||
String token,
|
||||
) async {
|
||||
try {
|
||||
await supabase
|
||||
.from('token')
|
||||
.upsert({'user_id': userId, tokenType: token});
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error en saveToken() - $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
44
lib/internationalization/internationalization.dart
Normal file
44
lib/internationalization/internationalization.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class AppLocalizations {
|
||||
AppLocalizations(this.locale);
|
||||
|
||||
final Locale locale;
|
||||
|
||||
static AppLocalizations of(BuildContext context) =>
|
||||
Localizations.of<AppLocalizations>(context, AppLocalizations)!;
|
||||
|
||||
static List<String> languages() => ['en'];
|
||||
|
||||
String get languageCode => locale.languageCode;
|
||||
int get languageIndex => languages().contains(languageCode)
|
||||
? languages().indexOf(languageCode)
|
||||
: 0;
|
||||
|
||||
String getText(String key) =>
|
||||
(kTranslationsMap[key] ?? {})[locale.languageCode] ?? '';
|
||||
|
||||
String getVariableText({
|
||||
String enText = '',
|
||||
}) =>
|
||||
[enText][languageIndex];
|
||||
}
|
||||
|
||||
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
const AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
AppLocalizations.languages().contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) =>
|
||||
SynchronousFuture<AppLocalizations>(AppLocalizations(locale));
|
||||
|
||||
@override
|
||||
bool shouldReload(AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
final kTranslationsMap =
|
||||
<Map<String, Map<String, String>>>[].reduce((a, b) => a..addAll(b));
|
||||
131
lib/main.dart
Normal file
131
lib/main.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:nethive_neo/helpers/scroll_behavior.dart';
|
||||
import 'package:nethive_neo/internationalization/internationalization.dart';
|
||||
import 'package:nethive_neo/router/router.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'package:nethive_neo/providers/user_provider.dart';
|
||||
import 'package:nethive_neo/providers/visual_state_provider.dart';
|
||||
import 'package:nethive_neo/providers/users_provider.dart';
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:url_strategy/url_strategy.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/constants.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setPathUrlStrategy();
|
||||
await Supabase.initialize(
|
||||
url: supabaseUrl,
|
||||
anonKey: anonKey,
|
||||
realtimeClientOptions: const RealtimeClientOptions(
|
||||
eventsPerSecond: 2,
|
||||
),
|
||||
);
|
||||
|
||||
supabaseLU = SupabaseClient(supabaseUrl, anonKey, schema: 'nethive');
|
||||
|
||||
await initGlobals();
|
||||
|
||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => UserState()),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => VisualStateProvider(context)),
|
||||
ChangeNotifierProvider(create: (_) => UsersProvider()),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
|
||||
static _MyAppState of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<_MyAppState>()!;
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
Locale _locale = const Locale('es');
|
||||
ThemeMode _themeMode = ThemeMode.light;
|
||||
|
||||
void setLocale(Locale value) => setState(() => _locale = value);
|
||||
void setThemeMode(ThemeMode mode) => setState(() => _themeMode = mode);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Nethive Neo',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: _themeMode,
|
||||
locale: _locale,
|
||||
home: const Placeholder(), // Cambia esto por tu pantalla inicial
|
||||
);
|
||||
}
|
||||
}
|
||||
/* class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
|
||||
static _MyAppState of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<_MyAppState>()!;
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
Locale _locale = const Locale('es');
|
||||
ThemeMode _themeMode = AppTheme.themeMode;
|
||||
|
||||
void setLocale(Locale value) => setState(() => _locale = value);
|
||||
void setThemeMode(ThemeMode mode) => setState(() {
|
||||
_themeMode = mode;
|
||||
AppTheme.saveThemeMode(mode);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Portal(
|
||||
child: MaterialApp.router(
|
||||
title: 'NETHIVE',
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: _locale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizationsDelegate(),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('en', 'US')],
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
dividerColor: Colors.grey,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
dividerColor: Colors.grey,
|
||||
),
|
||||
themeMode: _themeMode,
|
||||
routerConfig: router,
|
||||
scrollBehavior: MyCustomScrollBehavior(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} */
|
||||
34
lib/models/billing/billing_process.dart
Normal file
34
lib/models/billing/billing_process.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class BillingProcess {
|
||||
BillingProcess({
|
||||
required this.billingProcessId,
|
||||
required this.createdAt,
|
||||
required this.createdBy,
|
||||
required this.status,
|
||||
required this.customersBilled,
|
||||
required this.totalBilled,
|
||||
});
|
||||
|
||||
int billingProcessId;
|
||||
DateTime createdAt;
|
||||
String createdBy;
|
||||
String status;
|
||||
int customersBilled;
|
||||
num totalBilled;
|
||||
|
||||
factory BillingProcess.fromJson(String str) => BillingProcess.fromMap(json.decode(str));
|
||||
|
||||
factory BillingProcess.fromMap(Map<String, dynamic> json) {
|
||||
BillingProcess billingProcess = BillingProcess(
|
||||
billingProcessId: json["billing_process_id"],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
createdBy: json['created_by'],
|
||||
status: json['status'],
|
||||
customersBilled: json['customers_billed'],
|
||||
totalBilled: json['total_billed'],
|
||||
);
|
||||
|
||||
return billingProcess;
|
||||
}
|
||||
}
|
||||
133
lib/models/configuration.dart
Normal file
133
lib/models/configuration.dart
Normal file
@@ -0,0 +1,133 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Configuration {
|
||||
Config? config;
|
||||
|
||||
Configuration({
|
||||
this.config,
|
||||
});
|
||||
|
||||
factory Configuration.fromJson(String str) => Configuration.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Configuration.fromMap(Map<String, dynamic> json) => Configuration(
|
||||
config: json["config"] == null ? null : Config.fromMap(json["config"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"config": config?.toMap(),
|
||||
};
|
||||
}
|
||||
|
||||
class Config {
|
||||
Mode? dark;
|
||||
Mode? light;
|
||||
Logos? logos;
|
||||
|
||||
Config({
|
||||
this.dark,
|
||||
this.light,
|
||||
this.logos,
|
||||
});
|
||||
|
||||
factory Config.fromJson(String str) => Config.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Config.fromMap(Map<String, dynamic> json) => Config(
|
||||
dark: json["dark"] == null ? null : Mode.fromMap(json["dark"]),
|
||||
light: json["light"] == null ? null : Mode.fromMap(json["light"]),
|
||||
logos: json["logos"] == null ? null : Logos.fromMap(json["logos"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"dark": dark?.toMap(),
|
||||
"light": light?.toMap(),
|
||||
"logos": logos?.toMap(),
|
||||
};
|
||||
}
|
||||
|
||||
class Mode {
|
||||
String? hintText;
|
||||
String? alternate;
|
||||
String? primaryText;
|
||||
String? primaryColor;
|
||||
String? tertiaryText;
|
||||
String? secondaryText;
|
||||
String? tertiaryColor;
|
||||
String? secondaryColor;
|
||||
String? primaryBackground;
|
||||
String? tertiaryBackground;
|
||||
String? secondaryBackground;
|
||||
|
||||
Mode({
|
||||
this.hintText,
|
||||
this.alternate,
|
||||
this.primaryText,
|
||||
this.primaryColor,
|
||||
this.tertiaryText,
|
||||
this.secondaryText,
|
||||
this.tertiaryColor,
|
||||
this.secondaryColor,
|
||||
this.primaryBackground,
|
||||
this.tertiaryBackground,
|
||||
this.secondaryBackground,
|
||||
});
|
||||
|
||||
factory Mode.fromJson(String str) => Mode.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Mode.fromMap(Map<String, dynamic> json) => Mode(
|
||||
hintText: json["hintText"],
|
||||
alternate: json["alternate"],
|
||||
primaryText: json["primaryText"],
|
||||
primaryColor: json["primaryColor"],
|
||||
tertiaryText: json["tertiaryText"],
|
||||
secondaryText: json["secondaryText"],
|
||||
tertiaryColor: json["tertiaryColor"],
|
||||
secondaryColor: json["secondaryColor"],
|
||||
primaryBackground: json["primaryBackground"],
|
||||
tertiaryBackground: json["tertiaryBackground"],
|
||||
secondaryBackground: json["secondaryBackground"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"hintText": hintText,
|
||||
"alternate": alternate,
|
||||
"primaryText": primaryText,
|
||||
"primaryColor": primaryColor,
|
||||
"tertiaryText": tertiaryText,
|
||||
"secondaryText": secondaryText,
|
||||
"tertiaryColor": tertiaryColor,
|
||||
"secondaryColor": secondaryColor,
|
||||
"primaryBackground": primaryBackground,
|
||||
"tertiaryBackground": tertiaryBackground,
|
||||
"secondaryBackground": secondaryBackground,
|
||||
};
|
||||
}
|
||||
|
||||
class Logos {
|
||||
String? logoColor;
|
||||
String? logoBlanco;
|
||||
|
||||
Logos({
|
||||
this.logoColor,
|
||||
this.logoBlanco,
|
||||
});
|
||||
|
||||
factory Logos.fromJson(String str) => Logos.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Logos.fromMap(Map<String, dynamic> json) => Logos(
|
||||
logoColor: json["logoColor"],
|
||||
logoBlanco: json["logoBlanco"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"logoColor": logoColor,
|
||||
"logoBlanco": logoBlanco,
|
||||
};
|
||||
}
|
||||
130
lib/models/content_manager/ad_by_genre.dart
Normal file
130
lib/models/content_manager/ad_by_genre.dart
Normal file
@@ -0,0 +1,130 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class AdsByGenre {
|
||||
String genreName;
|
||||
int videoCount;
|
||||
int rowNumber;
|
||||
List<Video> videos;
|
||||
int genreId;
|
||||
dynamic genrePoster;
|
||||
String? storageCategoryImageFileName;
|
||||
bool isExpanded = false;
|
||||
AdsByGenre({
|
||||
required this.genreName,
|
||||
required this.videoCount,
|
||||
required this.rowNumber,
|
||||
required this.videos,
|
||||
required this.genreId,
|
||||
required this.genrePoster,
|
||||
required this.storageCategoryImageFileName,
|
||||
});
|
||||
|
||||
factory AdsByGenre.fromJson(String str) =>
|
||||
AdsByGenre.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory AdsByGenre.fromMap(Map<String, dynamic> json) => AdsByGenre(
|
||||
genreName: json["genre_name"],
|
||||
videoCount: json["video_count"],
|
||||
rowNumber: json["row_number"],
|
||||
videos: List<Video>.from(json["videos"].map((x) => Video.fromMap(x))),
|
||||
genreId: json["genre_id"],
|
||||
genrePoster: json["genre_poster"],
|
||||
storageCategoryImageFileName: json["poster_image_file"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"genre_name": genreName,
|
||||
"video_count": videoCount,
|
||||
"row_number": rowNumber,
|
||||
"videos": List<dynamic>.from(videos.map((x) => x.toMap())),
|
||||
"genre_id": genreId,
|
||||
"genre_poster": genrePoster,
|
||||
"poster_image_file": storageCategoryImageFileName,
|
||||
};
|
||||
}
|
||||
|
||||
class Video {
|
||||
String title;
|
||||
int points;
|
||||
dynamic status;
|
||||
dynamic urlAd;
|
||||
dynamic partner;
|
||||
int duration;
|
||||
String overview;
|
||||
int priority;
|
||||
int videoId;
|
||||
String videoUrl;
|
||||
List<String> categories;
|
||||
DateTime createdAt;
|
||||
String posterPath;
|
||||
bool videoStatus;
|
||||
dynamic expirationDate;
|
||||
String videoFileName;
|
||||
String posterFileName;
|
||||
|
||||
Video({
|
||||
required this.title,
|
||||
required this.points,
|
||||
required this.status,
|
||||
required this.urlAd,
|
||||
required this.partner,
|
||||
required this.duration,
|
||||
required this.overview,
|
||||
required this.priority,
|
||||
required this.videoId,
|
||||
required this.videoUrl,
|
||||
required this.categories,
|
||||
required this.createdAt,
|
||||
required this.posterPath,
|
||||
required this.videoStatus,
|
||||
required this.expirationDate,
|
||||
required this.videoFileName,
|
||||
required this.posterFileName,
|
||||
});
|
||||
|
||||
factory Video.fromJson(String str) => Video.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Video.fromMap(Map<String, dynamic> json) => Video(
|
||||
title: json["title"],
|
||||
points: json["points"],
|
||||
status: json["status"],
|
||||
urlAd: json["url_ad"],
|
||||
partner: json["partner"],
|
||||
duration: json["duration"],
|
||||
overview: json["overview"],
|
||||
priority: json["priority"],
|
||||
videoId: json["video_id"],
|
||||
videoUrl: json["video_url"],
|
||||
categories: List<String>.from(json["categories"].map((x) => x)),
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
posterPath: json["poster_path"],
|
||||
videoStatus: json["video_status"],
|
||||
expirationDate: json["expiration_date"],
|
||||
videoFileName: json["video_file_name"],
|
||||
posterFileName: json["poster_file_name"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"title": title,
|
||||
"points": points,
|
||||
"status": status,
|
||||
"url_ad": urlAd,
|
||||
"partner": partner,
|
||||
"duration": duration,
|
||||
"overview": overview,
|
||||
"priority": priority,
|
||||
"video_id": videoId,
|
||||
"video_url": videoUrl,
|
||||
"categories": List<dynamic>.from(categories.map((x) => x)),
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"poster_path": posterPath,
|
||||
"video_status": videoStatus,
|
||||
"expiration_date": expirationDate,
|
||||
"video_file_name": videoFileName,
|
||||
"poster_file_name": posterFileName,
|
||||
};
|
||||
}
|
||||
125
lib/models/content_manager/all_ads_one_table_model.dart
Normal file
125
lib/models/content_manager/all_ads_one_table_model.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class AllAdsOneTableModel {
|
||||
final int id;
|
||||
final int? rowNumber; // Opcional, para el campo row_number
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String overview;
|
||||
final String posterPath;
|
||||
final String title;
|
||||
final String video; // Corresponde a video_url
|
||||
final int durationVideo;
|
||||
final dynamic urlAd; // Añadido para url_ad
|
||||
final int priority;
|
||||
final bool videoStatus; // Cambiado de status text a videoStatus boolean
|
||||
final dynamic expirationDate;
|
||||
final int points;
|
||||
final String? videoFileName;
|
||||
final dynamic partner;
|
||||
final String posterFileName;
|
||||
final List<dynamic> categories;
|
||||
final List<Map<String, dynamic>>
|
||||
qrCodes; // Puede ser List<dynamic> o Map<String, dynamic>
|
||||
final int warningCount;
|
||||
|
||||
AllAdsOneTableModel({
|
||||
required this.id,
|
||||
this.rowNumber,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.overview,
|
||||
required this.posterPath,
|
||||
required this.title,
|
||||
required this.video,
|
||||
required this.durationVideo,
|
||||
this.urlAd, // Añadido
|
||||
required this.priority,
|
||||
required this.videoStatus, // Requerido y de tipo bool
|
||||
this.expirationDate,
|
||||
required this.points,
|
||||
this.videoFileName,
|
||||
this.partner,
|
||||
required this.posterFileName,
|
||||
required this.categories,
|
||||
required this.qrCodes,
|
||||
required this.warningCount,
|
||||
});
|
||||
|
||||
factory AllAdsOneTableModel.fromJson(String str) =>
|
||||
AllAdsOneTableModel.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory AllAdsOneTableModel.fromMap(Map<String, dynamic> json) {
|
||||
// Procesamiento de qrCodes similar al de CouponsModel
|
||||
final rawQrCodes = json["qr_codes"];
|
||||
|
||||
final parsedQrCodes = rawQrCodes is List
|
||||
? rawQrCodes
|
||||
.map((e) {
|
||||
if (e is String) {
|
||||
try {
|
||||
return Map<String, dynamic>.from(jsonDecode(e));
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
} else if (e is Map) {
|
||||
return Map<String, dynamic>.from(e);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.toList()
|
||||
.cast<Map<String, dynamic>>() // 🔥 necesario sí o sí
|
||||
: [];
|
||||
|
||||
return AllAdsOneTableModel(
|
||||
id: json["video_id"] ?? json["id"],
|
||||
rowNumber: json["row_number"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
updatedAt: DateTime.parse(json["updated_at"]),
|
||||
overview: json["overview"],
|
||||
posterPath: json["poster_path"],
|
||||
title: json["title"],
|
||||
video: json["video_url"],
|
||||
durationVideo: json["duration_video"],
|
||||
urlAd: json["url_ad"], // Mapeo para url_ad
|
||||
priority: json["priority"],
|
||||
videoStatus: json["video_status"], // Mapeo para video_status
|
||||
expirationDate: json["expiration_date"],
|
||||
points: json["points"],
|
||||
videoFileName: json["video_file_name"],
|
||||
partner: json["partner"],
|
||||
posterFileName: json["poster_file_name"],
|
||||
categories: json["categories"] == null
|
||||
? []
|
||||
: List<dynamic>.from(json["categories"].map((x) => x)),
|
||||
qrCodes: parsedQrCodes.cast<Map<String, dynamic>>(),
|
||||
warningCount: json["warning_count"],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"video_id": id,
|
||||
"row_number": rowNumber,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"overview": overview,
|
||||
"poster_path": posterPath,
|
||||
"title": title,
|
||||
"video_url": video,
|
||||
"duration_video": durationVideo,
|
||||
"url_ad": urlAd, // Añadido
|
||||
"priority": priority,
|
||||
"video_status": videoStatus,
|
||||
"expiration_date": expirationDate,
|
||||
"points": points,
|
||||
"video_file_name": videoFileName,
|
||||
"partner": partner,
|
||||
"poster_file_name": posterFileName,
|
||||
"categories": List<dynamic>.from(categories.map((x) => x)),
|
||||
"qr_codes": qrCodes,
|
||||
"warning_count": warningCount,
|
||||
};
|
||||
}
|
||||
83
lib/models/content_manager/all_books_one_table_model.dart
Normal file
83
lib/models/content_manager/all_books_one_table_model.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class AllBooksOneTableModel {
|
||||
int rowNumber;
|
||||
int bookId;
|
||||
DateTime createdAt;
|
||||
String bookDescription;
|
||||
String title;
|
||||
String book;
|
||||
String size;
|
||||
String year;
|
||||
String? bookCover;
|
||||
int autorFk;
|
||||
int statusFk;
|
||||
String bookStatus;
|
||||
String autorFirstName;
|
||||
String autorLastName;
|
||||
String? autorFullName;
|
||||
List<String?> categories;
|
||||
|
||||
AllBooksOneTableModel({
|
||||
required this.rowNumber,
|
||||
required this.bookId,
|
||||
required this.createdAt,
|
||||
required this.bookDescription,
|
||||
required this.title,
|
||||
required this.book,
|
||||
required this.size,
|
||||
required this.year,
|
||||
required this.bookCover,
|
||||
required this.autorFk,
|
||||
required this.statusFk,
|
||||
required this.bookStatus,
|
||||
required this.autorFirstName,
|
||||
required this.autorLastName,
|
||||
required this.autorFullName,
|
||||
required this.categories,
|
||||
});
|
||||
|
||||
factory AllBooksOneTableModel.fromJson(String str) =>
|
||||
AllBooksOneTableModel.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory AllBooksOneTableModel.fromMap(Map<String, dynamic> json) =>
|
||||
AllBooksOneTableModel(
|
||||
rowNumber: json["row_number"],
|
||||
bookId: json["book_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
bookDescription: json["book_description"],
|
||||
title: json["title"],
|
||||
book: json["book"],
|
||||
size: json["size"],
|
||||
year: json["year"],
|
||||
bookCover: json["book_cover"],
|
||||
autorFk: json["autor_fk"],
|
||||
statusFk: json["status_fk"],
|
||||
bookStatus: json["book_status"],
|
||||
autorFirstName: json["autor_first_name"],
|
||||
autorLastName: json["autor_last_name"],
|
||||
autorFullName: json["autor_name"],
|
||||
categories: List<String?>.from(json["categories"].map((x) => x)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"row_number": rowNumber,
|
||||
"book_id": bookId,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"book_description": bookDescription,
|
||||
"title": title,
|
||||
"book": book,
|
||||
"size": size,
|
||||
"year": year,
|
||||
"book_cover": bookCover,
|
||||
"autor_fk": autorFk,
|
||||
"status_fk": statusFk,
|
||||
"book_status": bookStatus,
|
||||
"autor_first_name": autorFirstName,
|
||||
"autor_last_name": autorLastName,
|
||||
"autor_name": autorFullName,
|
||||
"categories": List<dynamic>.from(categories.map((x) => x)),
|
||||
};
|
||||
}
|
||||
69
lib/models/content_manager/book.dart
Normal file
69
lib/models/content_manager/book.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Book {
|
||||
int bookId;
|
||||
String title;
|
||||
String bookDescription;
|
||||
DateTime createdAt;
|
||||
String book;
|
||||
String bookCover;
|
||||
String size;
|
||||
String year;
|
||||
String bookStatus;
|
||||
int autorId;
|
||||
String autorFirstName;
|
||||
String? autorLastName;
|
||||
String category;
|
||||
|
||||
Book({
|
||||
required this.bookId,
|
||||
required this.title,
|
||||
required this.bookDescription,
|
||||
required this.createdAt,
|
||||
required this.book,
|
||||
required this.bookCover,
|
||||
required this.size,
|
||||
required this.year,
|
||||
required this.bookStatus,
|
||||
required this.autorId,
|
||||
required this.autorFirstName,
|
||||
required this.autorLastName,
|
||||
required this.category,
|
||||
});
|
||||
|
||||
factory Book.fromJson(String str) => Book.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Book.fromMap(Map<String, dynamic> json) => Book(
|
||||
bookId: json["book_id"],
|
||||
title: json["title"],
|
||||
bookDescription: json["book_description"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
book: json["book"],
|
||||
bookCover: json["book_cover"],
|
||||
size: json["size"],
|
||||
year: json["year"],
|
||||
bookStatus: json["book_status"],
|
||||
autorId: json["autor_id"],
|
||||
autorFirstName: json["autor_first_name"],
|
||||
autorLastName: json["autor_last_name"],
|
||||
category: json["category"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"book_id": bookId,
|
||||
"title": title,
|
||||
"book_description": bookDescription,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"book": book,
|
||||
"book_cover": bookCover,
|
||||
"size": size,
|
||||
"year": year,
|
||||
"book_status": bookStatus,
|
||||
"autor_id": autorId,
|
||||
"autor_first_name": autorFirstName,
|
||||
"autor_last_name": autorLastName,
|
||||
"category": category,
|
||||
};
|
||||
}
|
||||
124
lib/models/content_manager/book_by_genre.dart
Normal file
124
lib/models/content_manager/book_by_genre.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class BooksByGenreModel {
|
||||
String genreName;
|
||||
int bookCount;
|
||||
int rowNumber;
|
||||
List<Book> books;
|
||||
int genreId;
|
||||
dynamic genrePoster;
|
||||
dynamic posterImageFile;
|
||||
bool isExpanded = false;
|
||||
|
||||
BooksByGenreModel({
|
||||
required this.genreName,
|
||||
required this.bookCount,
|
||||
required this.rowNumber,
|
||||
required this.books,
|
||||
required this.genreId,
|
||||
required this.genrePoster,
|
||||
required this.posterImageFile,
|
||||
});
|
||||
|
||||
factory BooksByGenreModel.fromJson(String str) =>
|
||||
BooksByGenreModel.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory BooksByGenreModel.fromMap(Map<String, dynamic> json) =>
|
||||
BooksByGenreModel(
|
||||
genreName: json["genre_name"],
|
||||
bookCount: json["book_count"],
|
||||
rowNumber: json["row_number"],
|
||||
books: List<Book>.from(json["books"].map((x) => Book.fromMap(x))),
|
||||
genreId: json["genre_id"],
|
||||
genrePoster: json["genre_poster"],
|
||||
posterImageFile: json["poster_image_file"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"genre_name": genreName,
|
||||
"book_count": bookCount,
|
||||
"row_number": rowNumber,
|
||||
"books": List<dynamic>.from(books.map((x) => x.toMap())),
|
||||
"genre_id": genreId,
|
||||
"genre_poster": genrePoster,
|
||||
"poster_image_file": posterImageFile,
|
||||
};
|
||||
}
|
||||
|
||||
class Book {
|
||||
String size;
|
||||
String year;
|
||||
String title;
|
||||
String status;
|
||||
int bookId;
|
||||
int autorId;
|
||||
String bookUrl;
|
||||
String overview;
|
||||
int statusId;
|
||||
String? bookCover;
|
||||
List<String> categories;
|
||||
DateTime createdAt;
|
||||
String autorLastName;
|
||||
String autorFirstName;
|
||||
String? autorFullName = '';
|
||||
|
||||
Book({
|
||||
required this.size,
|
||||
required this.year,
|
||||
required this.title,
|
||||
required this.status,
|
||||
required this.bookId,
|
||||
required this.autorId,
|
||||
required this.bookUrl,
|
||||
required this.overview,
|
||||
required this.statusId,
|
||||
required this.bookCover,
|
||||
required this.categories,
|
||||
required this.createdAt,
|
||||
required this.autorLastName,
|
||||
required this.autorFirstName,
|
||||
required this.autorFullName,
|
||||
});
|
||||
|
||||
factory Book.fromJson(String str) => Book.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Book.fromMap(Map<String, dynamic> json) => Book(
|
||||
size: json["size"],
|
||||
year: json["year"],
|
||||
title: json["title"],
|
||||
status: json["status"],
|
||||
bookId: json["book_id"],
|
||||
autorId: json["autor_id"],
|
||||
bookUrl: json["book_url"],
|
||||
overview: json["overview"],
|
||||
statusId: json["status_id"],
|
||||
bookCover: json["book_cover"],
|
||||
categories: List<String>.from(json["categories"].map((x) => x)),
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
autorLastName: json["autor_last_name"],
|
||||
autorFirstName: json["autor_first_name"],
|
||||
autorFullName: json["autor_name"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"size": size,
|
||||
"year": year,
|
||||
"title": title,
|
||||
"status": status,
|
||||
"book_id": bookId,
|
||||
"autor_id": autorId,
|
||||
"book_url": bookUrl,
|
||||
"overview": overview,
|
||||
"status_id": statusId,
|
||||
"book_cover": bookCover,
|
||||
"categories": List<dynamic>.from(categories.map((x) => x)),
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"autor_last_name": autorLastName,
|
||||
"autor_first_name": autorFirstName,
|
||||
"autor_name": autorFullName,
|
||||
};
|
||||
}
|
||||
30
lib/models/content_manager/category_model.dart
Normal file
30
lib/models/content_manager/category_model.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CategoryModel {
|
||||
int categoryId;
|
||||
DateTime createdAt;
|
||||
String name;
|
||||
|
||||
CategoryModel({
|
||||
required this.categoryId,
|
||||
required this.createdAt,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory CategoryModel.fromJson(String str) =>
|
||||
CategoryModel.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CategoryModel.fromMap(Map<String, dynamic> json) => CategoryModel(
|
||||
categoryId: json["category_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
name: json["name"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"category_id": categoryId,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"name": name,
|
||||
};
|
||||
}
|
||||
122
lib/models/coupons_manager/coupons_manager.dart
Normal file
122
lib/models/coupons_manager/coupons_manager.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CouponsModel {
|
||||
int rowNumber;
|
||||
int couponId;
|
||||
DateTime createdAt;
|
||||
DateTime updateAt;
|
||||
String title;
|
||||
String description;
|
||||
String termConditions;
|
||||
num discountValue;
|
||||
String discountTypeName;
|
||||
String discountTypeDescription;
|
||||
DateTime startDate;
|
||||
DateTime endDate;
|
||||
bool isActive;
|
||||
int usageLimit;
|
||||
String categoryName;
|
||||
bool categoryVisible;
|
||||
String categoryImageFile;
|
||||
bool videoRequired;
|
||||
dynamic couponImageFile;
|
||||
dynamic outLink;
|
||||
List<Map<String, dynamic>> qrCodes;
|
||||
|
||||
CouponsModel({
|
||||
required this.rowNumber,
|
||||
required this.couponId,
|
||||
required this.createdAt,
|
||||
required this.updateAt,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.termConditions,
|
||||
required this.discountValue,
|
||||
required this.discountTypeName,
|
||||
required this.discountTypeDescription,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.isActive,
|
||||
required this.usageLimit,
|
||||
required this.categoryName,
|
||||
required this.categoryVisible,
|
||||
required this.categoryImageFile,
|
||||
required this.videoRequired,
|
||||
required this.couponImageFile,
|
||||
required this.outLink,
|
||||
required this.qrCodes,
|
||||
});
|
||||
|
||||
factory CouponsModel.fromMap(Map<String, dynamic> json) {
|
||||
// Aseguramos que qrCodes sea una lista de Map<String, dynamic>
|
||||
final rawQrCodes = json["qr_codes"];
|
||||
|
||||
final parsedQrCodes = rawQrCodes is List
|
||||
? rawQrCodes
|
||||
.map((e) {
|
||||
if (e is String) {
|
||||
try {
|
||||
return Map<String, dynamic>.from(jsonDecode(e));
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
} else if (e is Map) {
|
||||
return Map<String, dynamic>.from(e);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.toList()
|
||||
.cast<Map<String, dynamic>>() // 🔥 necesario sí o sí
|
||||
: [];
|
||||
return CouponsModel(
|
||||
rowNumber: json["row_number"],
|
||||
couponId: json["coupon_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
updateAt: DateTime.parse(json["update_at"]),
|
||||
title: json["title"],
|
||||
description: json["description"],
|
||||
termConditions: json["term_conditions"],
|
||||
discountValue: json["discount_value"],
|
||||
discountTypeName: json["discount_type_name"],
|
||||
discountTypeDescription: json["discount_type_description"],
|
||||
startDate: DateTime.parse(json["start_date"]),
|
||||
endDate: DateTime.parse(json["end_date"]),
|
||||
isActive: json["is_active"],
|
||||
usageLimit: json["usage_limit"],
|
||||
categoryName: json["category_name"],
|
||||
categoryVisible: json["category_visible"],
|
||||
categoryImageFile: json["category_image_file"],
|
||||
videoRequired: json["video_required"],
|
||||
couponImageFile: json["coupon_image_file"],
|
||||
outLink: json["out_link"],
|
||||
qrCodes: parsedQrCodes.cast<Map<String, dynamic>>(),
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"row_number": rowNumber,
|
||||
"coupon_id": couponId,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"update_at": updateAt.toIso8601String(),
|
||||
"title": title,
|
||||
"description": description,
|
||||
"term_conditions": termConditions,
|
||||
"discount_value": discountValue,
|
||||
"discount_type_name": discountTypeName,
|
||||
"discount_type_description": discountTypeDescription,
|
||||
"start_date": startDate.toIso8601String(),
|
||||
"end_date": endDate.toIso8601String(),
|
||||
"is_active": isActive,
|
||||
"usage_limit": usageLimit,
|
||||
"category_name": categoryName,
|
||||
"category_visible": categoryVisible,
|
||||
"category_image_file": categoryImageFile,
|
||||
"video_required": videoRequired,
|
||||
"coupon_image_file": couponImageFile,
|
||||
"out_link": outLink,
|
||||
"qr_codes": qrCodes,
|
||||
};
|
||||
}
|
||||
93
lib/models/crm/clientes_model.dart
Normal file
93
lib/models/crm/clientes_model.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ClientesModel {
|
||||
int? customerId;
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
String? email;
|
||||
String? telefono;
|
||||
String? zipcode;
|
||||
DateTime? createdAt;
|
||||
String? direccion;
|
||||
String? ciudad;
|
||||
String? estado;
|
||||
String? proximaActualizacion;
|
||||
String? creadoPor;
|
||||
String? imagen;
|
||||
int? diasDisponibles;
|
||||
int? id;
|
||||
String? empresa;
|
||||
int? qr;
|
||||
String? service;
|
||||
String? status;
|
||||
|
||||
ClientesModel({
|
||||
this.customerId,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.email,
|
||||
this.telefono,
|
||||
this.zipcode,
|
||||
this.createdAt,
|
||||
this.direccion,
|
||||
this.ciudad,
|
||||
this.estado,
|
||||
this.proximaActualizacion,
|
||||
this.creadoPor,
|
||||
this.imagen,
|
||||
this.diasDisponibles,
|
||||
this.id,
|
||||
this.empresa,
|
||||
this.qr,
|
||||
this.service,
|
||||
this.status,
|
||||
});
|
||||
|
||||
factory ClientesModel.fromJson(String str) => ClientesModel.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory ClientesModel.fromMap(Map<String, dynamic> json) => ClientesModel(
|
||||
customerId: json["customer_id"],
|
||||
firstName: json["first_name"],
|
||||
lastName: json["last_name"],
|
||||
email: json["email"],
|
||||
telefono: json["telefono"],
|
||||
zipcode: json["zipcode"],
|
||||
createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
|
||||
direccion: json["direccion"],
|
||||
ciudad: json["ciudad"],
|
||||
estado: json["estado"],
|
||||
proximaActualizacion: json["proxima_actualizacion"],
|
||||
creadoPor: json["creado_por"],
|
||||
imagen: json["imagen"],
|
||||
diasDisponibles: json["dias_disponibles"],
|
||||
id: json["id"],
|
||||
empresa: json["empresa"],
|
||||
qr: json["qr"],
|
||||
service: json["service"],
|
||||
status: json["status"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"customer_id": customerId,
|
||||
"first_name": firstName,
|
||||
"last_name": lastName,
|
||||
"email": email,
|
||||
"telefono": telefono,
|
||||
"zipcode": zipcode,
|
||||
"created_at": createdAt?.toIso8601String(),
|
||||
"direccion": direccion,
|
||||
"ciudad": ciudad,
|
||||
"estado": estado,
|
||||
"proxima_actualizacion": proximaActualizacion,
|
||||
"creado_por": creadoPor,
|
||||
"imagen": imagen,
|
||||
"dias_disponibles": diasDisponibles,
|
||||
"id": id,
|
||||
"empresa": empresa,
|
||||
"qr": qr,
|
||||
"service": service,
|
||||
"status": status,
|
||||
};
|
||||
}
|
||||
44
lib/models/customers/address.dart
Normal file
44
lib/models/customers/address.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:nethive_neo/models/state.dart';
|
||||
|
||||
class Address {
|
||||
Address({
|
||||
required this.id,
|
||||
required this.address1,
|
||||
required this.address2,
|
||||
required this.zipcode,
|
||||
required this.city,
|
||||
required this.stateFk,
|
||||
required this.state,
|
||||
required this.country,
|
||||
});
|
||||
|
||||
int id;
|
||||
String address1;
|
||||
String? address2;
|
||||
String zipcode;
|
||||
String city;
|
||||
int stateFk;
|
||||
StateAPI state;
|
||||
String country;
|
||||
|
||||
String get fullAddress => '$address1 $city, ${state.code} $zipcode $country';
|
||||
|
||||
factory Address.fromJson(String str) => Address.fromMap(json.decode(str));
|
||||
|
||||
factory Address.fromMap(Map<String, dynamic> json) {
|
||||
Address address = Address(
|
||||
id: json["address_id"],
|
||||
address1: json['address_1'],
|
||||
address2: json['address_2'],
|
||||
zipcode: json['zipcode'],
|
||||
city: json['city'],
|
||||
stateFk: json['state_fk'],
|
||||
state: StateAPI.fromMap(json['state']),
|
||||
country: json['country'],
|
||||
);
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
31
lib/models/customers/bill_cycle.dart
Normal file
31
lib/models/customers/bill_cycle.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class BillCycle {
|
||||
BillCycle({
|
||||
required this.customerId,
|
||||
required this.day,
|
||||
required this.billDueDay,
|
||||
required this.graceDays,
|
||||
required this.frequency,
|
||||
});
|
||||
|
||||
int customerId;
|
||||
int day;
|
||||
int billDueDay;
|
||||
int graceDays;
|
||||
int frequency;
|
||||
|
||||
factory BillCycle.fromJson(String str) => BillCycle.fromMap(json.decode(str));
|
||||
|
||||
factory BillCycle.fromMap(Map<String, dynamic> json) {
|
||||
BillCycle billCycle = BillCycle(
|
||||
customerId: json["customer_id"],
|
||||
day: json['day'],
|
||||
billDueDay: json['bill_due_day'],
|
||||
graceDays: json['grace_days'],
|
||||
frequency: json['frequency'],
|
||||
);
|
||||
|
||||
return billCycle;
|
||||
}
|
||||
}
|
||||
33
lib/models/customers/credit_card.dart
Normal file
33
lib/models/customers/credit_card.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CreditCard {
|
||||
CreditCard({
|
||||
required this.creditCardId,
|
||||
required this.type,
|
||||
required this.token,
|
||||
required this.automatic,
|
||||
required this.customerFk,
|
||||
});
|
||||
|
||||
int creditCardId;
|
||||
String type;
|
||||
String token;
|
||||
bool automatic;
|
||||
int customerFk;
|
||||
|
||||
String get last4Digits => token.substring(token.length - 4);
|
||||
|
||||
factory CreditCard.fromJson(String str) => CreditCard.fromMap(json.decode(str));
|
||||
|
||||
factory CreditCard.fromMap(Map<String, dynamic> json) {
|
||||
CreditCard creditCard = CreditCard(
|
||||
creditCardId: json["credit_card_id"],
|
||||
type: json['type'] ?? 'Credit Card',
|
||||
token: json['token'],
|
||||
automatic: json['automatic'] ?? true,
|
||||
customerFk: json['customer_fk'],
|
||||
);
|
||||
|
||||
return creditCard;
|
||||
}
|
||||
}
|
||||
45
lib/models/customers/customer.dart
Normal file
45
lib/models/customers/customer.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Customer {
|
||||
Customer({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.createdAt,
|
||||
required this.status,
|
||||
required this.phoneNumber,
|
||||
required this.balance,
|
||||
this.image,
|
||||
});
|
||||
|
||||
int id;
|
||||
String firstName;
|
||||
String lastName;
|
||||
String email;
|
||||
DateTime createdAt;
|
||||
String status;
|
||||
String phoneNumber;
|
||||
num balance;
|
||||
String? image;
|
||||
|
||||
String get fullName => '$firstName $lastName';
|
||||
|
||||
factory Customer.fromJson(String str) => Customer.fromMap(json.decode(str));
|
||||
|
||||
factory Customer.fromMap(Map<String, dynamic> json) {
|
||||
Customer customer = Customer(
|
||||
id: json["customer_id"],
|
||||
firstName: json['first_name'],
|
||||
lastName: json['last_name'],
|
||||
email: json["email"],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
status: json['status'],
|
||||
phoneNumber: json['mobile_phone'],
|
||||
balance: json['balance'] ?? 0.00,
|
||||
image: json['image'],
|
||||
);
|
||||
|
||||
return customer;
|
||||
}
|
||||
}
|
||||
37
lib/models/customers/customer_dashboards.dart
Normal file
37
lib/models/customers/customer_dashboards.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CustomerDashboards {
|
||||
int? customerId;
|
||||
DateTime? createdAt;
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
String? status;
|
||||
|
||||
CustomerDashboards({
|
||||
this.customerId,
|
||||
this.createdAt,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.status,
|
||||
});
|
||||
|
||||
factory CustomerDashboards.fromJson(String str) => CustomerDashboards.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CustomerDashboards.fromMap(Map<String, dynamic> json) => CustomerDashboards(
|
||||
customerId: json["customer_id"],
|
||||
createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
|
||||
firstName: json["first_name"],
|
||||
lastName: json["last_name"],
|
||||
status: json["status"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"customer_id": customerId,
|
||||
"created_at": createdAt?.toIso8601String(),
|
||||
"first_name": firstName,
|
||||
"last_name": lastName,
|
||||
"status": status,
|
||||
};
|
||||
}
|
||||
72
lib/models/customers/customer_details.dart
Normal file
72
lib/models/customers/customer_details.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
|
||||
class CustomerDetails {
|
||||
CustomerDetails({
|
||||
required this.id,
|
||||
required this.createdDate,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.billingAddress,
|
||||
required this.status,
|
||||
required this.phoneNumber,
|
||||
required this.balance,
|
||||
this.image,
|
||||
required this.billCycle,
|
||||
required this.services,
|
||||
required this.notes,
|
||||
required this.messages,
|
||||
});
|
||||
|
||||
int id;
|
||||
DateTime createdDate;
|
||||
String firstName;
|
||||
String lastName;
|
||||
String email;
|
||||
Address billingAddress;
|
||||
String status;
|
||||
String phoneNumber;
|
||||
num balance;
|
||||
String? image;
|
||||
BillCycle billCycle;
|
||||
List<Service> services;
|
||||
List<Note> notes;
|
||||
List<Message> messages;
|
||||
|
||||
String get fullName => '$firstName $lastName';
|
||||
|
||||
DateTime get billingDate {
|
||||
final now = DateTime.now();
|
||||
return DateTime(now.year, now.month, billCycle.day);
|
||||
}
|
||||
|
||||
factory CustomerDetails.fromJson(String str) =>
|
||||
CustomerDetails.fromMap(json.decode(str));
|
||||
|
||||
factory CustomerDetails.fromMap(Map<String, dynamic> json) {
|
||||
CustomerDetails customer = CustomerDetails(
|
||||
id: json["customer_id"],
|
||||
createdDate: DateTime.parse(json['created_date']),
|
||||
firstName: json['first_name'],
|
||||
lastName: json['last_name'],
|
||||
email: json["email"],
|
||||
billingAddress: Address.fromMap(json['billing_address']),
|
||||
status: json['status'],
|
||||
phoneNumber: json['mobile_phone'],
|
||||
balance: json['balance'] ?? 0.00,
|
||||
image: json['image'],
|
||||
billCycle: BillCycle.fromMap(json['billing_cycle']),
|
||||
services: (json['services'] as List)
|
||||
.map((service) => Service.fromMap(service))
|
||||
.toList(),
|
||||
notes: (json['notes'] as List).map((note) => Note.fromMap(note)).toList(),
|
||||
messages: (json['messages'] as List)
|
||||
.map((note) => Message.fromMap(note))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return customer;
|
||||
}
|
||||
}
|
||||
73
lib/models/customers/customer_marcadores.dart
Normal file
73
lib/models/customers/customer_marcadores.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CustomerMarcadores {
|
||||
int? id;
|
||||
DateTime? createdAt;
|
||||
double? customersTotals;
|
||||
double? activeTotals;
|
||||
double? leadTotals;
|
||||
List<NewCustomersId>? newCustomersId;
|
||||
|
||||
CustomerMarcadores({
|
||||
this.id,
|
||||
this.createdAt,
|
||||
this.customersTotals,
|
||||
this.activeTotals,
|
||||
this.leadTotals,
|
||||
this.newCustomersId,
|
||||
});
|
||||
|
||||
factory CustomerMarcadores.fromJson(String str) => CustomerMarcadores.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CustomerMarcadores.fromMap(Map<String, dynamic> json) => CustomerMarcadores(
|
||||
id: json["id"],
|
||||
createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
|
||||
customersTotals: json["customers_totals"],
|
||||
activeTotals: json["active_totals"],
|
||||
leadTotals: json["lead_totals"],
|
||||
newCustomersId: json["new_customers_id"] == null ? [] : List<NewCustomersId>.from(json["new_customers_id"]!.map((x) => NewCustomersId.fromMap(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"id": id,
|
||||
"created_at": createdAt?.toIso8601String(),
|
||||
"customers_totals": customersTotals,
|
||||
"active_totals": activeTotals,
|
||||
"lead_totals": leadTotals,
|
||||
"new_customers_id": newCustomersId == null ? [] : List<dynamic>.from(newCustomersId!.map((x) => x.toMap())),
|
||||
};
|
||||
}
|
||||
|
||||
class NewCustomersId {
|
||||
int? count;
|
||||
String? status;
|
||||
DateTime? createdAt;
|
||||
List<int>? customerIds;
|
||||
|
||||
NewCustomersId({
|
||||
this.count,
|
||||
this.status,
|
||||
this.createdAt,
|
||||
this.customerIds,
|
||||
});
|
||||
|
||||
factory NewCustomersId.fromJson(String str) => NewCustomersId.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory NewCustomersId.fromMap(Map<String, dynamic> json) => NewCustomersId(
|
||||
count: json["count"],
|
||||
status: json["status"],
|
||||
createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
|
||||
customerIds: json["customer_ids"] == null ? [] : List<int>.from(json["customer_ids"]!.map((x) => x)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"count": count,
|
||||
"status": status,
|
||||
"created_at": "${createdAt!.year.toString().padLeft(4, '0')}-${createdAt!.month.toString().padLeft(2, '0')}-${createdAt!.day.toString().padLeft(2, '0')}",
|
||||
"customer_ids": customerIds == null ? [] : List<dynamic>.from(customerIds!.map((x) => x)),
|
||||
};
|
||||
}
|
||||
47
lib/models/customers/customer_subscriptions.dart
Normal file
47
lib/models/customers/customer_subscriptions.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CustomerSubscription {
|
||||
int subscriptionId;
|
||||
String description;
|
||||
double amount;
|
||||
String status;
|
||||
String createdAt;
|
||||
String periodStart;
|
||||
String? periodEnd;
|
||||
|
||||
CustomerSubscription({
|
||||
required this.subscriptionId,
|
||||
required this.description,
|
||||
required this.amount,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
required this.periodStart,
|
||||
required this.periodEnd,
|
||||
});
|
||||
|
||||
factory CustomerSubscription.fromJson(String str) =>
|
||||
CustomerSubscription.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CustomerSubscription.fromMap(Map<String, dynamic> json) =>
|
||||
CustomerSubscription(
|
||||
subscriptionId: json["subscription_id"],
|
||||
description: json["description"],
|
||||
amount: json["amount"],
|
||||
status: json["status"],
|
||||
createdAt: json["created_at"],
|
||||
periodStart: json["period_start"],
|
||||
periodEnd: json["period_end"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"subscription_id": subscriptionId,
|
||||
"description": description,
|
||||
"amount": amount,
|
||||
"status": status,
|
||||
"created_at": createdAt,
|
||||
"period_start": periodStart,
|
||||
"period_end": periodEnd,
|
||||
};
|
||||
}
|
||||
57
lib/models/customers/heatmap_customer_model.dart
Normal file
57
lib/models/customers/heatmap_customer_model.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class HeatmapCustomers {
|
||||
int? year;
|
||||
int? month;
|
||||
Registro? registro;
|
||||
|
||||
HeatmapCustomers({
|
||||
this.year,
|
||||
this.month,
|
||||
this.registro,
|
||||
});
|
||||
|
||||
factory HeatmapCustomers.fromJson(String str) => HeatmapCustomers.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory HeatmapCustomers.fromMap(Map<String, dynamic> json) => HeatmapCustomers(
|
||||
year: json["year"],
|
||||
month: json["month"],
|
||||
registro: json["registro"] == null ? null : Registro.fromMap(json["registro"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"year": year,
|
||||
"month": month,
|
||||
"registro": registro?.toMap(),
|
||||
};
|
||||
}
|
||||
|
||||
class Registro {
|
||||
double? leads;
|
||||
double? total;
|
||||
double? actives;
|
||||
|
||||
Registro({
|
||||
this.leads,
|
||||
this.total,
|
||||
this.actives,
|
||||
});
|
||||
|
||||
factory Registro.fromJson(String str) => Registro.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Registro.fromMap(Map<String, dynamic> json) => Registro(
|
||||
leads: json["Leads"],
|
||||
total: json["Total"],
|
||||
actives: json["Actives"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"Leads": leads,
|
||||
"Total": total,
|
||||
"Actives": actives,
|
||||
};
|
||||
}
|
||||
25
lib/models/customers/invoice.dart
Normal file
25
lib/models/customers/invoice.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Invoice {
|
||||
Invoice({
|
||||
required this.invoiceId,
|
||||
required this.date,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
int invoiceId;
|
||||
DateTime date;
|
||||
String code;
|
||||
|
||||
factory Invoice.fromJson(String str) => Invoice.fromMap(json.decode(str));
|
||||
|
||||
factory Invoice.fromMap(Map<String, dynamic> json) {
|
||||
Invoice invoice = Invoice(
|
||||
invoiceId: json['invoice_id'],
|
||||
date: DateTime.parse(json["created_at"]),
|
||||
code: json['invoice'],
|
||||
);
|
||||
|
||||
return invoice;
|
||||
}
|
||||
}
|
||||
26
lib/models/customers/messages.dart
Normal file
26
lib/models/customers/messages.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Message {
|
||||
Message({
|
||||
required this.messageId,
|
||||
required this.createdAt,
|
||||
required this.customerFk,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
int messageId;
|
||||
DateTime createdAt;
|
||||
int customerFk;
|
||||
String text;
|
||||
|
||||
factory Message.fromJson(String str) => Message.fromMap(json.decode(str));
|
||||
|
||||
factory Message.fromMap(Map<String, dynamic> json) {
|
||||
return Message(
|
||||
messageId: json["notification_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
customerFk: json['from'],
|
||||
text: json['message'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/models/customers/note.dart
Normal file
28
lib/models/customers/note.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Note {
|
||||
Note({
|
||||
required this.noteId,
|
||||
required this.createdAt,
|
||||
required this.customerFk,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
int noteId;
|
||||
DateTime createdAt;
|
||||
int customerFk;
|
||||
String text;
|
||||
|
||||
factory Note.fromJson(String str) => Note.fromMap(json.decode(str));
|
||||
|
||||
factory Note.fromMap(Map<String, dynamic> json) {
|
||||
Note note = Note(
|
||||
noteId: json["note_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
customerFk: json['customer_fk'],
|
||||
text: json['note'] ?? '',
|
||||
);
|
||||
|
||||
return note;
|
||||
}
|
||||
}
|
||||
44
lib/models/customers/payment.dart
Normal file
44
lib/models/customers/payment.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Payment {
|
||||
Payment({
|
||||
required this.paymentId,
|
||||
required this.transactionId,
|
||||
required this.date,
|
||||
required this.method,
|
||||
required this.description,
|
||||
required this.status,
|
||||
required this.amount,
|
||||
required this.error,
|
||||
});
|
||||
|
||||
int transactionId;
|
||||
int paymentId;
|
||||
DateTime date;
|
||||
String method;
|
||||
String description;
|
||||
String status;
|
||||
num amount;
|
||||
String error;
|
||||
|
||||
// String get displayValue => creditCard == '-' ? creditCard : '****$last4Digits';
|
||||
|
||||
// String get last4Digits => creditCard.substring(creditCard.length - 4);
|
||||
|
||||
factory Payment.fromJson(String str) => Payment.fromMap(json.decode(str));
|
||||
|
||||
factory Payment.fromMap(Map<String, dynamic> json) {
|
||||
Payment payment = Payment(
|
||||
paymentId: json['payment_id'],
|
||||
transactionId: json['transaction_id'],
|
||||
date: DateTime.parse(json["date"]),
|
||||
method: json['method'],
|
||||
description: json['description'],
|
||||
status: json['status'],
|
||||
amount: json['amount'],
|
||||
error: json['error'] ?? '',
|
||||
);
|
||||
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
26
lib/models/customers/recent_activity.dart
Normal file
26
lib/models/customers/recent_activity.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class RecentActivity {
|
||||
RecentActivity({
|
||||
required this.recentActivityId,
|
||||
required this.createdAt,
|
||||
required this.customerFk,
|
||||
required this.activity,
|
||||
});
|
||||
|
||||
int recentActivityId;
|
||||
DateTime createdAt;
|
||||
int customerFk;
|
||||
String activity;
|
||||
|
||||
factory RecentActivity.fromJson(String str) => RecentActivity.fromMap(json.decode(str));
|
||||
|
||||
factory RecentActivity.fromMap(Map<String, dynamic> json) {
|
||||
return RecentActivity(
|
||||
recentActivityId: json["recent_activity_id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
customerFk: json['customer_fk'],
|
||||
activity: json['activity'],
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/models/customers/transaction.dart
Normal file
30
lib/models/customers/transaction.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Transaction {
|
||||
Transaction(
|
||||
{required this.date,
|
||||
required this.description,
|
||||
required this.type,
|
||||
required this.amount,
|
||||
required this.status});
|
||||
|
||||
DateTime date;
|
||||
String description;
|
||||
String type;
|
||||
num amount;
|
||||
String status;
|
||||
|
||||
factory Transaction.fromJson(String str) =>
|
||||
Transaction.fromMap(json.decode(str));
|
||||
|
||||
factory Transaction.fromMap(Map<String, dynamic> json) {
|
||||
Transaction transaction = Transaction(
|
||||
date: DateTime.parse(json["created_at"]),
|
||||
description: json['description'],
|
||||
type: json['type_transaction'],
|
||||
amount: json['total_amout'],
|
||||
status: json['status']);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
73
lib/models/form_custom_field.dart
Normal file
73
lib/models/form_custom_field.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class FormCustomField {
|
||||
int? formId;
|
||||
int? fillId;
|
||||
int fieldId;
|
||||
String fieldName;
|
||||
String? fieldType;
|
||||
String? fieldIcon;
|
||||
IconData? fieldIconData;
|
||||
bool? fieldVisible;
|
||||
int? fieldOrder;
|
||||
List<String>? enumValues;
|
||||
bool? enumVisible;
|
||||
int? fieldValueId;
|
||||
String? fieldValue;
|
||||
List<String> fieldValues = [];
|
||||
TextEditingController textEditingController = TextEditingController();
|
||||
|
||||
FormCustomField({
|
||||
this.formId,
|
||||
this.fillId,
|
||||
required this.fieldId,
|
||||
required this.fieldName,
|
||||
this.fieldType,
|
||||
this.fieldIcon,
|
||||
this.fieldIconData,
|
||||
this.fieldVisible,
|
||||
this.fieldOrder,
|
||||
this.enumValues,
|
||||
this.enumVisible,
|
||||
this.fieldValueId,
|
||||
this.fieldValue,
|
||||
});
|
||||
|
||||
factory FormCustomField.fromJson(String str) => FormCustomField.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory FormCustomField.fromMap(Map<String, dynamic> json) => FormCustomField(
|
||||
formId: json["form_id"],
|
||||
fillId: json["fill_id"],
|
||||
fieldId: json["field_id"],
|
||||
fieldName: json["field_name"],
|
||||
fieldType: json["field_type"],
|
||||
fieldIconData: json["field_icon"] != null ? IconData(int.parse(json["field_icon"]), fontFamily: 'MaterialIcons') : null,
|
||||
fieldIcon: json["field_icon"],
|
||||
fieldVisible: json["field_visible"],
|
||||
fieldOrder: json["field_order"],
|
||||
fieldValueId: json["field_value_id"],
|
||||
fieldValue: json["field_value"],
|
||||
enumValues: json["enum_values"] == null ? [] : List<String>.from(json["enum_values"]!.map((x) => x)),
|
||||
enumVisible: json["enum_visible"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"form_id": formId,
|
||||
"fill_id": fillId,
|
||||
"field_id": fieldId,
|
||||
"field_name": fieldName,
|
||||
"field_type": fieldType,
|
||||
"field_icon": fieldIcon,
|
||||
"field_visible": fieldVisible,
|
||||
"field_order": fieldOrder,
|
||||
"field_value_id": fieldValueId,
|
||||
"field_value": fieldValue,
|
||||
"enum_values": enumValues == null ? [] : List<dynamic>.from(enumValues!.map((x) => x)),
|
||||
"enum_visible": enumVisible,
|
||||
};
|
||||
}
|
||||
21
lib/models/models.dart
Normal file
21
lib/models/models.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
export 'package:nethive_neo/models/customers/recent_activity.dart';
|
||||
export 'package:nethive_neo/models/customers/messages.dart';
|
||||
export 'package:nethive_neo/models/customers/invoice.dart';
|
||||
export 'package:nethive_neo/models/customers/payment.dart';
|
||||
export 'package:nethive_neo/models/customers/transaction.dart';
|
||||
export 'package:nethive_neo/models/customers/credit_card.dart';
|
||||
export 'package:nethive_neo/models/billing/billing_process.dart';
|
||||
export 'package:nethive_neo/models/customers/note.dart';
|
||||
export 'package:nethive_neo/models/customers/customer_details.dart';
|
||||
export 'package:nethive_neo/models/service.dart';
|
||||
export 'package:nethive_neo/models/customers/bill_cycle.dart';
|
||||
export 'package:nethive_neo/models/state.dart';
|
||||
export 'package:nethive_neo/models/customers/address.dart';
|
||||
export 'package:nethive_neo/models/customers/customer.dart';
|
||||
export 'package:nethive_neo/models/configuration.dart';
|
||||
export 'package:nethive_neo/models/users/user.dart';
|
||||
export 'package:nethive_neo/models/users/role.dart';
|
||||
export 'package:nethive_neo/models/users/token.dart';
|
||||
export 'package:nethive_neo/models/content_manager/ad_by_genre.dart';
|
||||
export 'package:nethive_neo/models/content_manager/all_ads_one_table_model.dart';
|
||||
export 'package:nethive_neo/models/content_manager/category_model.dart';
|
||||
61
lib/models/service.dart
Normal file
61
lib/models/service.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Service {
|
||||
Service({
|
||||
required this.serviceId,
|
||||
required this.code,
|
||||
required this.name,
|
||||
this.description,
|
||||
required this.transactionTypeFk,
|
||||
required this.type,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
int serviceId;
|
||||
String code;
|
||||
String name;
|
||||
String? description;
|
||||
int transactionTypeFk;
|
||||
String? type;
|
||||
num value;
|
||||
|
||||
factory Service.fromJson(String str) => Service.fromMap(json.decode(str));
|
||||
|
||||
factory Service.fromMap(Map<String, dynamic> json) {
|
||||
Service service = Service(
|
||||
serviceId: json["service_id"],
|
||||
code: json['code'],
|
||||
name: json['name'],
|
||||
description: json['description'],
|
||||
transactionTypeFk: json['transaction_type_fk'],
|
||||
type: json['type'],
|
||||
value: json['value'],
|
||||
);
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomerService {
|
||||
CustomerService({
|
||||
required this.quantity,
|
||||
required this.createdAt,
|
||||
required this.details,
|
||||
});
|
||||
|
||||
int quantity;
|
||||
DateTime createdAt;
|
||||
Service details;
|
||||
|
||||
factory CustomerService.fromJson(String str) => CustomerService.fromMap(json.decode(str));
|
||||
|
||||
factory CustomerService.fromMap(Map<String, dynamic> json) {
|
||||
CustomerService customerService = CustomerService(
|
||||
quantity: json["quantity"],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
details: Service.fromMap(json),
|
||||
);
|
||||
|
||||
return customerService;
|
||||
}
|
||||
}
|
||||
23
lib/models/state.dart
Normal file
23
lib/models/state.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class StateAPI {
|
||||
StateAPI({
|
||||
required this.id,
|
||||
required this.code,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
int id;
|
||||
String code;
|
||||
String name;
|
||||
|
||||
factory StateAPI.fromJson(String str) => StateAPI.fromMap(json.decode(str));
|
||||
|
||||
factory StateAPI.fromMap(Map<String, dynamic> json) {
|
||||
return StateAPI(
|
||||
id: json["state_id"],
|
||||
code: json['code'],
|
||||
name: json['name'],
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/models/support/support.dart
Normal file
43
lib/models/support/support.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class CustomerSupport {
|
||||
int? customerId;
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
String? email;
|
||||
String? mobilePhone;
|
||||
String? address;
|
||||
|
||||
CustomerSupport({
|
||||
this.customerId,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.email,
|
||||
this.mobilePhone,
|
||||
this.address,
|
||||
});
|
||||
String get completeName => '$firstName $lastName';
|
||||
|
||||
factory CustomerSupport.fromJson(String str) =>
|
||||
CustomerSupport.fromMap(json.decode(str));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CustomerSupport.fromMap(Map<String, dynamic> json) => CustomerSupport(
|
||||
customerId: json["customer_id"],
|
||||
firstName: json["first_name"],
|
||||
lastName: json["last_name"],
|
||||
email: json["email"],
|
||||
mobilePhone: json["mobile_phone"],
|
||||
address: json["address"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"customer_id": customerId,
|
||||
"first_name": firstName,
|
||||
"last_name": lastName,
|
||||
"email": email,
|
||||
"mobile_phone": mobilePhone,
|
||||
"address": address,
|
||||
};
|
||||
}
|
||||
78
lib/models/users/role.dart
Normal file
78
lib/models/users/role.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Role {
|
||||
Role({
|
||||
required this.name,
|
||||
required this.roleId,
|
||||
required this.permissions,
|
||||
});
|
||||
|
||||
String name;
|
||||
int roleId;
|
||||
Permissions permissions;
|
||||
|
||||
factory Role.fromJson(String str) => Role.fromMap(json.decode(str));
|
||||
|
||||
factory Role.fromMap(Map<String, dynamic> json) => Role(
|
||||
name: json["name"],
|
||||
roleId: json["role_id"],
|
||||
permissions: Permissions.fromMap(json["permissions"]),
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Role && other.name == name && other.roleId == roleId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(name, roleId, permissions);
|
||||
}
|
||||
|
||||
class Permissions {
|
||||
Permissions({
|
||||
required this.home,
|
||||
required this.crm,
|
||||
required this.qr,
|
||||
required this.orders,
|
||||
required this.inventory,
|
||||
required this.serviceOrder,
|
||||
required this.support,
|
||||
required this.sales,
|
||||
required this.billing,
|
||||
required this.technical,
|
||||
required this.users,
|
||||
required this.limited,
|
||||
});
|
||||
|
||||
String? home;
|
||||
String? crm;
|
||||
String? qr;
|
||||
String? orders;
|
||||
String? inventory;
|
||||
String? serviceOrder;
|
||||
String? sales;
|
||||
String? support;
|
||||
String? billing;
|
||||
String? users;
|
||||
String? technical;
|
||||
bool? limited;
|
||||
|
||||
factory Permissions.fromJson(String str) =>
|
||||
Permissions.fromMap(json.decode(str));
|
||||
|
||||
factory Permissions.fromMap(Map<String, dynamic> json) => Permissions(
|
||||
home: json['Home'],
|
||||
crm: json['CRM'],
|
||||
qr: json['QR'],
|
||||
orders: json['Orders'],
|
||||
inventory: json['Inventory'],
|
||||
serviceOrder: json['Service Order'],
|
||||
sales: json['Sales'],
|
||||
support: json['Support'],
|
||||
billing: json['Billing'],
|
||||
users: json['Users'],
|
||||
technical: json["Technical"],
|
||||
limited: json["Limited"],
|
||||
);
|
||||
}
|
||||
60
lib/models/users/token.dart
Normal file
60
lib/models/users/token.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
|
||||
class Token {
|
||||
Token({
|
||||
required this.token,
|
||||
required this.userId,
|
||||
required this.email,
|
||||
required this.created,
|
||||
});
|
||||
|
||||
String token;
|
||||
String userId;
|
||||
String email;
|
||||
DateTime created;
|
||||
|
||||
factory Token.fromJson(String str, String token) =>
|
||||
Token.fromMap(json.decode(str), token);
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Token.fromMap(Map<String, dynamic> payload, String token) {
|
||||
return Token(
|
||||
token: token,
|
||||
userId: payload["user_id"],
|
||||
email: payload["email"],
|
||||
created: DateTime.parse(payload['created']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"user_id": userId,
|
||||
"email": email,
|
||||
"created": created,
|
||||
};
|
||||
|
||||
Future<bool> validate(String type) async {
|
||||
int timeLimit = 5;
|
||||
|
||||
try {
|
||||
final minutesPassed =
|
||||
DateTime.now().toUtc().difference(created).inMinutes;
|
||||
if (minutesPassed < timeLimit) {
|
||||
final res = await supabase
|
||||
.from('token')
|
||||
.select('token_$type')
|
||||
.eq('user_id', userId);
|
||||
final validatedToken = res[0]['token_$type'];
|
||||
if (token == validatedToken) return true;
|
||||
}
|
||||
} catch (e) {
|
||||
log('Error en validateToken - $e');
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
89
lib/models/users/user.dart
Normal file
89
lib/models/users/user.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:nethive_neo/models/users/role.dart';
|
||||
|
||||
class User {
|
||||
User({
|
||||
required this.id,
|
||||
required this.sequentialId,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.role,
|
||||
required this.mobilePhone,
|
||||
required this.status,
|
||||
this.image,
|
||||
});
|
||||
|
||||
String id;
|
||||
int sequentialId;
|
||||
String firstName;
|
||||
String lastName;
|
||||
Role role;
|
||||
String email;
|
||||
String? mobilePhone;
|
||||
String status;
|
||||
String? image;
|
||||
|
||||
String get fullName => '$firstName $lastName';
|
||||
|
||||
int get statusColor {
|
||||
late final int color;
|
||||
switch (status) {
|
||||
case 'Active':
|
||||
color = 0XFF2EA437;
|
||||
break;
|
||||
default:
|
||||
color = 0XFF2EA437;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
bool get isAdmin => role.name == 'Administrator';
|
||||
bool get isInventory => role.name == 'Inventory Warehouse';
|
||||
bool get isSales => role.name == 'Sales Rep';
|
||||
bool get isSupport => role.name == 'Support';
|
||||
bool get isOperation => role.name == 'Operation';
|
||||
|
||||
factory User.fromJson(String str) => User.fromMap(json.decode(str));
|
||||
|
||||
factory User.fromMap(Map<String, dynamic> json) {
|
||||
User user = User(
|
||||
id: json["user_profile_id"],
|
||||
sequentialId: json["sequential_id"],
|
||||
firstName: json['first_name'],
|
||||
lastName: json['last_name'],
|
||||
role: Role.fromMap(json['role']),
|
||||
email: json["email"],
|
||||
mobilePhone: json['mobile_phone'],
|
||||
status: json['status'],
|
||||
image: json['image'],
|
||||
);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
User copyWith({
|
||||
String? id,
|
||||
int? sequentialId,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
Role? role,
|
||||
String? email,
|
||||
String? mobilePhone,
|
||||
String? status,
|
||||
String? image,
|
||||
}) {
|
||||
return User(
|
||||
id: id ?? this.id,
|
||||
sequentialId: sequentialId ?? this.sequentialId,
|
||||
firstName: firstName ?? this.firstName,
|
||||
lastName: lastName ?? this.lastName,
|
||||
role: role ?? this.role,
|
||||
email: email ?? this.email,
|
||||
mobilePhone: mobilePhone ?? this.mobilePhone,
|
||||
status: status ?? this.status,
|
||||
image: image ?? this.image,
|
||||
);
|
||||
}
|
||||
}
|
||||
78
lib/pages/login_page/login_page.dart
Normal file
78
lib/pages/login_page/login_page.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:nethive_neo/models/users/token.dart';
|
||||
import 'package:nethive_neo/pages/login_page/widgets/login_form.dart';
|
||||
import 'package:nethive_neo/pages/login_page/widgets/right_image.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({
|
||||
Key? key,
|
||||
this.token,
|
||||
}) : super(key: key);
|
||||
|
||||
final Token? token;
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: scaffoldKey,
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth < 600) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryBackground,
|
||||
borderRadius: BorderRadius.circular(19),
|
||||
),
|
||||
child: Center(
|
||||
child: LoginForm(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryBackground,
|
||||
borderRadius: BorderRadius.circular(19),
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
top: 101,
|
||||
left: 109,
|
||||
child: LoginForm(),
|
||||
),
|
||||
const Positioned(
|
||||
right: 0,
|
||||
child: RightImageWidget(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
357
lib/pages/login_page/widgets/login_form.dart
Normal file
357
lib/pages/login_page/widgets/login_form.dart
Normal file
@@ -0,0 +1,357 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' as sf;
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/helpers/supabase/queries.dart';
|
||||
import 'package:nethive_neo/pages/widgets/custom_button.dart';
|
||||
import 'package:nethive_neo/providers/providers.dart';
|
||||
import 'package:nethive_neo/services/api_error_handler.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class LoginForm extends StatefulWidget {
|
||||
const LoginForm({super.key});
|
||||
|
||||
@override
|
||||
State<LoginForm> createState() => _LoginFormState();
|
||||
}
|
||||
|
||||
class _LoginFormState extends State<LoginForm> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool passwordVisibility = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final UserState userState = Provider.of<UserState>(context);
|
||||
double height = MediaQuery.of(context).size.height / 1024;
|
||||
double width = MediaQuery.of(context).size.width / 1440;
|
||||
|
||||
Future<void> login() async {
|
||||
//Login
|
||||
try {
|
||||
// Check if user exists
|
||||
final userId =
|
||||
await userState.getUserId(userState.emailController.text);
|
||||
|
||||
if (userId == null) {
|
||||
await ApiErrorHandler.callToast('Este Correo no está registrado');
|
||||
return;
|
||||
}
|
||||
|
||||
await supabase.auth.signInWithPassword(
|
||||
email: userState.emailController.text,
|
||||
password: userState.passwordController.text,
|
||||
);
|
||||
|
||||
if (userState.recuerdame == true) {
|
||||
await userState.setEmail();
|
||||
await userState.setPassword();
|
||||
} else {
|
||||
userState.emailController.text = '';
|
||||
userState.passwordController.text = '';
|
||||
await prefs.remove('email');
|
||||
await prefs.remove('password');
|
||||
}
|
||||
|
||||
if (supabase.auth.currentUser == null) {
|
||||
await ApiErrorHandler.callToast();
|
||||
return;
|
||||
}
|
||||
|
||||
currentUser = await SupabaseQueries.getCurrentUserData();
|
||||
|
||||
if (currentUser == null) {
|
||||
await ApiErrorHandler.callToast();
|
||||
return;
|
||||
}
|
||||
/*
|
||||
13: limitado
|
||||
14: ilimitado
|
||||
*/
|
||||
|
||||
/* ILIMITADO */
|
||||
print('User Role ID: ${currentUser!.role.roleId}');
|
||||
/* if (currentUser!.role.roleId == 14 || currentUser!.role.roleId == 13) {
|
||||
context.pushReplacement('/book_page_main');
|
||||
return;
|
||||
} */
|
||||
/* LIMITADO */
|
||||
|
||||
theme = await SupabaseQueries.getUserTheme();
|
||||
AppTheme.initConfiguration(theme);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
context.pushReplacement('/');
|
||||
} catch (e) {
|
||||
if (e is sf.AuthException) {
|
||||
await userState.incrementLoginAttempts(
|
||||
userState.emailController.text,
|
||||
);
|
||||
await ApiErrorHandler.callToast('Credenciales Invalidas');
|
||||
|
||||
return;
|
||||
}
|
||||
log('Error al iniciar sesion - $e');
|
||||
}
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 521,
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(
|
||||
child: Image.asset(
|
||||
AppTheme.themeMode == ThemeMode.light
|
||||
? 'assets/images/logo_lu.jpeg'
|
||||
: 'assets/images/logo_lu.jpeg',
|
||||
filterQuality: FilterQuality.high,
|
||||
fit: BoxFit.contain,
|
||||
alignment: Alignment.centerLeft,
|
||||
width: width * 200,
|
||||
height: height * 200,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Inicie Sesión',
|
||||
textAlign: TextAlign.start,
|
||||
style: AppTheme.of(context).title3.override(
|
||||
fontFamily: AppTheme.of(context).title3Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50.32),
|
||||
Text(
|
||||
'Correo',
|
||||
style: AppTheme.of(context).bodyText2,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: userState.emailController,
|
||||
onFieldSubmitted: (value) async {
|
||||
if (!formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
await login();
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'El correo es requerido';
|
||||
} else if (!EmailValidator.validate(value)) {
|
||||
return 'Favor de ingresar un correo valido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
decoration: _buildInputDecoration('Nombre de usuario'),
|
||||
style: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Contraseña',
|
||||
style: AppTheme.of(context).bodyText2,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextFormField(
|
||||
controller: userState.passwordController,
|
||||
obscureText: !passwordVisibility,
|
||||
onFieldSubmitted: (value) async {
|
||||
if (!formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
await login();
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'La contraseña es requerida';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
decoration:
|
||||
_buildInputDecoration('Contraseña', isPassword: true),
|
||||
style: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 53),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.25,
|
||||
child: Checkbox(
|
||||
value: userState.recuerdame,
|
||||
activeColor: AppTheme.of(context).primaryColor,
|
||||
onChanged: (value) async {
|
||||
await userState.updateRecuerdame();
|
||||
},
|
||||
splashRadius: 0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await userState.updateRecuerdame();
|
||||
},
|
||||
child: Text(
|
||||
'Recuerdame',
|
||||
style: AppTheme.of(context).bodyText3,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Text(
|
||||
'Olvidaste Contraseña?',
|
||||
style: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomButton(
|
||||
onPressed: () async {
|
||||
if (!formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
await login();
|
||||
},
|
||||
text: 'Iniciar Sesión',
|
||||
options: ButtonOptions(
|
||||
width: double.infinity,
|
||||
height: 68,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
textStyle: AppTheme.of(context).bodyText2.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).primaryBackground,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 68),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'No registrasdo aun? ',
|
||||
style: AppTheme.of(context).bodyText2.override(
|
||||
fontFamily: AppTheme.of(context).bodyText2Family,
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Text(
|
||||
'Registrate',
|
||||
style: AppTheme.of(context).bodyText2.override(
|
||||
fontFamily: AppTheme.of(context).bodyText2Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.copyright,
|
||||
size: 16,
|
||||
color: Color(0xFF99B2C6),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Copyright CB Luna 2024',
|
||||
style: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: AppTheme.of(context).alternate,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _buildInputDecoration(String label,
|
||||
{bool isPassword = false}) {
|
||||
Widget? suffixIcon;
|
||||
if (isPassword) {
|
||||
suffixIcon = InkWell(
|
||||
onTap: () => setState(
|
||||
() => passwordVisibility = !passwordVisibility,
|
||||
),
|
||||
focusNode: FocusNode(skipTraversal: true),
|
||||
child: Icon(
|
||||
passwordVisibility
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: const Color(0xFFB8B8B8),
|
||||
size: 22,
|
||||
),
|
||||
);
|
||||
}
|
||||
return InputDecoration(
|
||||
hintText: label,
|
||||
filled: true,
|
||||
// isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 27, vertical: 20),
|
||||
fillColor: AppTheme.of(context).tertiaryBackground,
|
||||
hintStyle: GoogleFonts.quicksand(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
color: AppTheme.of(context).hintText,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.red,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.only(left: 27, right: 19),
|
||||
child: Icon(
|
||||
isPassword ? Icons.lock : Icons.mail_rounded,
|
||||
size: 24,
|
||||
color: AppTheme.of(context).hintText,
|
||||
),
|
||||
),
|
||||
suffixIcon: suffixIcon,
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/pages/login_page/widgets/right_image.dart
Normal file
39
lib/pages/login_page/widgets/right_image.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
//import 'package:cbluna_crm_lu/theme/theme.dart';
|
||||
|
||||
class RightImageWidget extends StatelessWidget {
|
||||
const RightImageWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: size.width * 0.55,
|
||||
height: size.height - 20,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(19),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/lu_login.jpeg'),
|
||||
filterQuality: FilterQuality.high,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/* Positioned(
|
||||
left: 51.54,
|
||||
bottom: 86,
|
||||
child: Text(
|
||||
'Control de Visitas',
|
||||
style: AppTheme.of(context).title1.override(
|
||||
fontFamily: AppTheme.of(context).title1Family,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
), */
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/pages/page_not_found.dart
Normal file
19
lib/pages/page_not_found.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class PageNotFoundPage extends StatelessWidget {
|
||||
const PageNotFoundPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'404 - Page not found',
|
||||
style: GoogleFonts.montserratAlternates(
|
||||
fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
2
lib/pages/pages.dart
Normal file
2
lib/pages/pages.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'package:nethive_neo/pages/login_page/login_page.dart';
|
||||
export 'package:nethive_neo/pages/page_not_found.dart';
|
||||
93
lib/pages/widgets/animated_hover_button.dart
Normal file
93
lib/pages/widgets/animated_hover_button.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedHoverButton extends StatefulWidget {
|
||||
const AnimatedHoverButton({
|
||||
Key? key,
|
||||
required this.primaryColor,
|
||||
required this.secondaryColor,
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
required this.tooltip,
|
||||
this.radius = 50,
|
||||
this.size = 50,
|
||||
this.enable = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
final void Function() onTap;
|
||||
final IconData icon;
|
||||
final String tooltip;
|
||||
final double radius;
|
||||
final double size;
|
||||
final bool? enable;
|
||||
|
||||
@override
|
||||
State<AnimatedHoverButton> createState() => _AnimatedHoverButtonState();
|
||||
}
|
||||
|
||||
class _AnimatedHoverButtonState extends State<AnimatedHoverButton> {
|
||||
bool isHovered = false;
|
||||
|
||||
void setHovered(bool hovered) {
|
||||
setState(() {
|
||||
isHovered = hovered;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
|
||||
if (!isHovered) {
|
||||
if (widget.enable == true) {
|
||||
primaryColor = widget.primaryColor;
|
||||
secondaryColor = widget.secondaryColor;
|
||||
} else {
|
||||
primaryColor = Theme.of(context).hintColor;
|
||||
secondaryColor = Theme.of(context).scaffoldBackgroundColor;
|
||||
}
|
||||
} else {
|
||||
if (widget.enable == true) {
|
||||
primaryColor = widget.secondaryColor;
|
||||
secondaryColor = widget.primaryColor;
|
||||
} else {
|
||||
primaryColor = Theme.of(context).scaffoldBackgroundColor;
|
||||
secondaryColor = Theme.of(context).hintColor;
|
||||
}
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: widget.tooltip,
|
||||
child: GestureDetector(
|
||||
onTap: widget.enable == true ? widget.onTap : null,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setHovered(true),
|
||||
onExit: (_) => setHovered(false),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
color: secondaryColor,
|
||||
borderRadius: BorderRadius.circular(widget.radius),
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
color: primaryColor,
|
||||
size: widget.size / 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
110
lib/pages/widgets/animated_hover_icon_text_button.dart
Normal file
110
lib/pages/widgets/animated_hover_icon_text_button.dart
Normal file
@@ -0,0 +1,110 @@
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedHoverIconTextButton extends StatefulWidget {
|
||||
const AnimatedHoverIconTextButton({
|
||||
Key? key,
|
||||
required this.primaryColor,
|
||||
required this.secondaryColor,
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.tooltip,
|
||||
this.size = 45,
|
||||
this.enable = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
final void Function() onTap;
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final String? tooltip;
|
||||
final double? size;
|
||||
final bool? enable;
|
||||
|
||||
@override
|
||||
State<AnimatedHoverIconTextButton> createState() =>
|
||||
_AnimatedHoverButtonState();
|
||||
}
|
||||
|
||||
class _AnimatedHoverButtonState extends State<AnimatedHoverIconTextButton> {
|
||||
late Color primaryColor;
|
||||
late Color secondaryColor;
|
||||
|
||||
void setColors(bool isPrimary) {
|
||||
if (isPrimary) {
|
||||
if (widget.enable == true) {
|
||||
primaryColor = widget.primaryColor;
|
||||
secondaryColor = widget.secondaryColor;
|
||||
} else {
|
||||
primaryColor = AppTheme.of(context).hintText;
|
||||
secondaryColor = AppTheme.of(context).primaryBackground;
|
||||
}
|
||||
} else {
|
||||
if (widget.enable == true) {
|
||||
primaryColor = widget.secondaryColor;
|
||||
secondaryColor = widget.primaryColor;
|
||||
} else {
|
||||
primaryColor = AppTheme.of(context).primaryBackground;
|
||||
secondaryColor = AppTheme.of(context).hintText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
primaryColor = widget.primaryColor;
|
||||
secondaryColor = widget.secondaryColor;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: widget.tooltip,
|
||||
child: GestureDetector(
|
||||
onTap: widget.enable == true ? widget.onTap : null,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => setColors(false)),
|
||||
onExit: (_) => setState(() => setColors(true)),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
//width: (MediaQuery.of(context).size.width * 48 / 1920),
|
||||
//height: (MediaQuery.of(context).size.width * 48 / 1920),
|
||||
decoration: BoxDecoration(
|
||||
color: secondaryColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
widget.icon,
|
||||
color: primaryColor,
|
||||
size: (MediaQuery.of(context).size.width * 25 / 1920),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
widget.text,
|
||||
style: AppTheme.of(context).bodyText3.override(
|
||||
fontFamily: AppTheme.of(context).bodyText3Family,
|
||||
color: primaryColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
185
lib/pages/widgets/custom_button.dart
Normal file
185
lib/pages/widgets/custom_button.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ButtonOptions {
|
||||
ButtonOptions({
|
||||
this.textStyle,
|
||||
this.elevation,
|
||||
this.height = 50,
|
||||
this.width,
|
||||
this.padding,
|
||||
required this.color,
|
||||
this.disabledColor = const Color(0xFF57636C),
|
||||
this.disabledTextColor = const Color(0xFFc1c7cc),
|
||||
this.splashColor,
|
||||
this.iconSize,
|
||||
this.iconColor,
|
||||
this.iconPadding,
|
||||
this.borderRadius,
|
||||
this.borderSide,
|
||||
this.disabled = false,
|
||||
});
|
||||
|
||||
final TextStyle? textStyle;
|
||||
final double? elevation;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Color color;
|
||||
final Color disabledColor;
|
||||
final Color disabledTextColor;
|
||||
final Color? splashColor;
|
||||
final double? iconSize;
|
||||
final Color? iconColor;
|
||||
final EdgeInsetsGeometry? iconPadding;
|
||||
final BorderRadius? borderRadius;
|
||||
final BorderSide? borderSide;
|
||||
final bool disabled;
|
||||
}
|
||||
|
||||
class CustomButton extends StatefulWidget {
|
||||
const CustomButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
this.icon,
|
||||
this.iconData,
|
||||
required this.options,
|
||||
this.showLoadingIndicator = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final Widget? icon;
|
||||
final IconData? iconData;
|
||||
final Function() onPressed;
|
||||
final ButtonOptions options;
|
||||
final bool showLoadingIndicator;
|
||||
|
||||
@override
|
||||
State<CustomButton> createState() => _CustomButtonState();
|
||||
}
|
||||
|
||||
class _CustomButtonState extends State<CustomButton> {
|
||||
bool loading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget textWidget = loading
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 23,
|
||||
height: 23,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
widget.options.textStyle?.color ?? Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: AutoSizeText(
|
||||
widget.text,
|
||||
style: widget.options.textStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
|
||||
final onPressed =
|
||||
widget.options.disabled || (widget.showLoadingIndicator && loading)
|
||||
? null
|
||||
: () async {
|
||||
if (!widget.showLoadingIndicator) {
|
||||
widget.onPressed();
|
||||
return;
|
||||
}
|
||||
setState(() => loading = true);
|
||||
try {
|
||||
await widget.onPressed();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ButtonStyle style = ButtonStyle(
|
||||
shape: MaterialStateProperty.all<OutlinedBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
widget.options.borderRadius ?? BorderRadius.circular(8.0),
|
||||
side: widget.options.borderSide ?? BorderSide.none,
|
||||
),
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) ||
|
||||
widget.options.disabled) {
|
||||
return widget.options.disabledTextColor;
|
||||
}
|
||||
return widget.options.textStyle?.color;
|
||||
},
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) ||
|
||||
widget.options.disabled) {
|
||||
return widget.options.disabledColor;
|
||||
}
|
||||
return widget.options.color;
|
||||
},
|
||||
),
|
||||
overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
|
||||
if (widget.options.disabled) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return widget.options.splashColor;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
padding: MaterialStateProperty.all(widget.options.padding ??
|
||||
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0)),
|
||||
elevation:
|
||||
MaterialStateProperty.all<double>(widget.options.elevation ?? 2.0),
|
||||
mouseCursor: MaterialStateProperty.resolveWith<MouseCursor?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (widget.options.disabled) {
|
||||
return SystemMouseCursors.basic;
|
||||
}
|
||||
return SystemMouseCursors.click;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.icon != null || widget.iconData != null) {
|
||||
return SizedBox(
|
||||
height: widget.options.height,
|
||||
width: widget.options.width,
|
||||
child: ElevatedButton.icon(
|
||||
icon: Padding(
|
||||
padding: widget.options.iconPadding ?? EdgeInsets.zero,
|
||||
child: widget.icon ??
|
||||
Icon(
|
||||
widget.iconData,
|
||||
size: widget.options.iconSize,
|
||||
color: widget.options.iconColor ??
|
||||
widget.options.textStyle?.color,
|
||||
),
|
||||
),
|
||||
label: textWidget,
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: widget.options.height,
|
||||
width: widget.options.width,
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
child: textWidget,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
3
lib/providers/providers.dart
Normal file
3
lib/providers/providers.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export 'package:nethive_neo/providers/visual_state_provider.dart';
|
||||
export 'package:nethive_neo/providers/users_provider.dart';
|
||||
export 'package:nethive_neo/providers/user_provider.dart';
|
||||
383
lib/providers/user_provider.dart
Normal file
383
lib/providers/user_provider.dart
Normal file
@@ -0,0 +1,383 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/helpers/supabase/queries.dart';
|
||||
import 'package:nethive_neo/router/router.dart';
|
||||
|
||||
class UserState extends ChangeNotifier {
|
||||
//EMAIL
|
||||
Future<void> setEmail() async {
|
||||
await prefs.setString('email', emailController.text);
|
||||
}
|
||||
|
||||
//Controlador para LoginScreen
|
||||
TextEditingController emailController = TextEditingController();
|
||||
|
||||
//PASSWORD
|
||||
|
||||
Future<void> setPassword() async {
|
||||
await prefs.setString('password', passwordController.text);
|
||||
}
|
||||
|
||||
//Controlador para LoginScreen
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
|
||||
bool recuerdame = false;
|
||||
|
||||
//Variables para editar perfil
|
||||
TextEditingController nombrePerfil = TextEditingController();
|
||||
TextEditingController apellidosPerfil = TextEditingController();
|
||||
TextEditingController telefonoPerfil = TextEditingController();
|
||||
TextEditingController extensionPerfil = TextEditingController();
|
||||
TextEditingController emailPerfil = TextEditingController();
|
||||
TextEditingController contrasenaAnteriorPerfil = TextEditingController();
|
||||
TextEditingController confirmarContrasenaPerfil = TextEditingController();
|
||||
TextEditingController contrasenaPerfil = TextEditingController();
|
||||
|
||||
String? imageName;
|
||||
Uint8List? webImage;
|
||||
|
||||
int loginAttempts = 0;
|
||||
|
||||
bool userChangedPasswordInLast90Days = true;
|
||||
|
||||
//Constructor de provider
|
||||
UserState() {
|
||||
recuerdame = prefs.getBool('recuerdame') ?? false;
|
||||
|
||||
if (recuerdame == true) {
|
||||
emailController.text = prefs.getString('email') ?? '';
|
||||
passwordController.text = prefs.getString('password') ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> actualizarContrasena() async {
|
||||
try {
|
||||
final res = await supabase.rpc('change_user_password', params: {
|
||||
'current_plain_password': contrasenaAnteriorPerfil.text,
|
||||
'new_plain_password': contrasenaPerfil.text,
|
||||
});
|
||||
if (res == null) {
|
||||
log('Error en actualizarContrasena()');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error en actualizarContrasena() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// void initPerfilUsuario() {
|
||||
// if (currentUser == null) return;
|
||||
// nombrePerfil.text = currentUser!.nombre;
|
||||
// // apellidosPerfil.text = currentUser!.apellidos;
|
||||
// emailPerfil.text = currentUser!.email;
|
||||
// webImage = null;
|
||||
// contrasenaPerfil.clear();
|
||||
// contrasenaAnteriorPerfil.clear();
|
||||
// confirmarContrasenaPerfil.clear();
|
||||
// }
|
||||
|
||||
Future<void> selectImage() async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
final XFile? pickedImage = await picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
);
|
||||
|
||||
if (pickedImage == null) return;
|
||||
|
||||
final String fileExtension = p.extension(pickedImage.name);
|
||||
const uuid = Uuid();
|
||||
final String fileName = uuid.v1();
|
||||
imageName = 'avatar-$fileName$fileExtension';
|
||||
|
||||
webImage = await pickedImage.readAsBytes();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearImage() {
|
||||
webImage = null;
|
||||
imageName = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<String?> uploadImage() async {
|
||||
if (webImage != null && imageName != null) {
|
||||
await supabase.storage.from('avatars').uploadBinary(
|
||||
imageName!,
|
||||
webImage!,
|
||||
fileOptions: const FileOptions(
|
||||
cacheControl: '3600',
|
||||
upsert: false,
|
||||
),
|
||||
);
|
||||
|
||||
return imageName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool> editarPerfilDeUsuario() async {
|
||||
try {
|
||||
await supabase.from('perfil_usuario').update(
|
||||
{
|
||||
'nombre': nombrePerfil.text,
|
||||
'apellidos': apellidosPerfil.text,
|
||||
'telefono': telefonoPerfil.text,
|
||||
'imagen': imageName,
|
||||
},
|
||||
).eq('perfil_usuario_id', currentUser!.id);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error en editarPerfilDeUsuario() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateRecuerdame() async {
|
||||
recuerdame = !recuerdame;
|
||||
await prefs.setBool('recuerdame', recuerdame);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String generateToken(String userId, String email) {
|
||||
//Generar token
|
||||
final jwt = JWT(
|
||||
{
|
||||
'user_id': userId,
|
||||
'email': email,
|
||||
'created': DateTime.now().toUtc().toIso8601String(),
|
||||
},
|
||||
issuer: 'https://github.com/jonasroussel/dart_jsonwebtoken',
|
||||
);
|
||||
|
||||
// Sign it (default with HS256 algorithm)
|
||||
return jwt.sign(SecretKey('secret'));
|
||||
}
|
||||
|
||||
// Future<bool> sendEmailWithToken(String email, String password, String token, String type) async {
|
||||
// //Mandar correo
|
||||
// final response = await http.post(
|
||||
// Uri.parse(bonitaConnectionUrl),
|
||||
// body: json.encode(
|
||||
// {
|
||||
// "user": "Web",
|
||||
// "action": "bonitaBpmCaseVariables",
|
||||
// 'process': 'Alta_de_Usuario',
|
||||
// 'data': {
|
||||
// 'variables': [
|
||||
// {
|
||||
// 'name': 'correo',
|
||||
// 'value': email,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'password',
|
||||
// 'value': password,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'token',
|
||||
// 'value': token,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'type',
|
||||
// 'value': type,
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// if (response.statusCode > 204) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// Future<bool> sendEmailWithAccessCode(String email, String id) async {
|
||||
// //Mandar correo
|
||||
// final response = await http.post(
|
||||
// Uri.parse(bonitaConnectionUrl),
|
||||
// body: json.encode(
|
||||
// {
|
||||
// "user": "Web",
|
||||
// "action": "bonitaBpmCaseVariables",
|
||||
// 'process': 'DVLogin',
|
||||
// 'data': {
|
||||
// 'variables': [
|
||||
// {
|
||||
// 'name': 'correo',
|
||||
// 'value': email,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'id',
|
||||
// 'value': id,
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// if (response.statusCode > 204) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
Future<Map<String, String>?> resetPassword(String email) async {
|
||||
try {
|
||||
final res = await supabase.from('users').select('id').eq('email', email);
|
||||
if ((res as List).isEmpty) {
|
||||
return {'Error': 'El correo no está registrado'};
|
||||
}
|
||||
|
||||
final userId = res[0]['id'];
|
||||
|
||||
if (userId == null) return {'Error': 'El correo no está registrado'};
|
||||
|
||||
final token = generateToken(userId, email);
|
||||
|
||||
// Guardar token
|
||||
await SupabaseQueries.saveToken(
|
||||
userId,
|
||||
'token_reset',
|
||||
token,
|
||||
);
|
||||
|
||||
// final res2 = await sendEmailWithToken(email, '', token, 'reset');
|
||||
|
||||
// if (!res2) return {'Error': 'Error al realizar petición'};
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
log('Error en resetPassword() - $e');
|
||||
}
|
||||
return {'Error': 'There was an error after sending request'};
|
||||
}
|
||||
|
||||
Future<String?> getUserId(String email) async {
|
||||
try {
|
||||
final res = await supabase.from('users').select('id').eq('email', email);
|
||||
if ((res as List).isNotEmpty) {
|
||||
return res[0]['id'];
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
log('Error en getUserId - $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> validateAccessCode(String userId, String accessCode) async {
|
||||
try {
|
||||
final res = await supabase.rpc('validate_access_code', params: {
|
||||
'id': userId,
|
||||
'access_code_attempt': accessCode,
|
||||
});
|
||||
return res;
|
||||
} catch (e) {
|
||||
log('Error en getAccessCode() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> sendAccessCode(String userId) async {
|
||||
try {
|
||||
final codeSaved = await supabase.rpc(
|
||||
'save_access_code',
|
||||
params: {'id': userId},
|
||||
);
|
||||
if (!codeSaved) return false;
|
||||
|
||||
// final emailSent = await sendEmailWithAccessCode(
|
||||
// emailController.text,
|
||||
// userId,
|
||||
// );
|
||||
|
||||
// if (!emailSent) return false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error en sendEmailWithAccessCode() -$e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> incrementLoginAttempts(String email) async {
|
||||
loginAttempts += 1;
|
||||
if (loginAttempts >= 3) {
|
||||
try {
|
||||
await supabase.rpc('block_user', params: {'email': email});
|
||||
} catch (e) {
|
||||
log('Error en incrementLoginAttempts() - $e');
|
||||
} finally {
|
||||
loginAttempts = 0;
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> registerLogin(String userId) async {
|
||||
try {
|
||||
await supabase.from('login_historico').insert({'usuario_fk': userId});
|
||||
} catch (e) {
|
||||
log('registerLogin() - $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkIfUserBlocked(String email) async {
|
||||
try {
|
||||
final res = await supabase.rpc('check_if_user_blocked', params: {
|
||||
'email': email,
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
log('Error en checkIfUserIsBlocked() - $e');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkIfUserChangedPasswordInLast90Days(String userId) async {
|
||||
try {
|
||||
final res = await supabase.rpc('usuario_cambio_contrasena', params: {
|
||||
'user_id': userId,
|
||||
});
|
||||
|
||||
userChangedPasswordInLast90Days = res;
|
||||
} catch (e) {
|
||||
log('Error en checkIfUserChangedPasswordInLast90Days() - $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await supabase.auth.signOut();
|
||||
currentUser = null;
|
||||
await prefs.remove('currentRol');
|
||||
/* Configuration? conf = await SupabaseQueries.getDefaultTheme();
|
||||
AppTheme.initConfiguration(conf); */
|
||||
router.pushReplacement('/');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
|
||||
nombrePerfil.dispose();
|
||||
emailPerfil.dispose();
|
||||
contrasenaPerfil.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
370
lib/providers/users_provider.dart
Normal file
370
lib/providers/users_provider.dart
Normal file
@@ -0,0 +1,370 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:random_password_generator/random_password_generator.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:pluto_grid/pluto_grid.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' hide User;
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:nethive_neo/helpers/constants.dart';
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
import 'package:nethive_neo/services/api_error_handler.dart';
|
||||
|
||||
class UsersProvider extends ChangeNotifier {
|
||||
PlutoGridStateManager? stateManager;
|
||||
List<PlutoRow> rows = [];
|
||||
|
||||
//CREATE USUARIO
|
||||
TextEditingController nameController = TextEditingController();
|
||||
TextEditingController lastNameController = TextEditingController();
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController phoneController = TextEditingController();
|
||||
Role? selectedRole;
|
||||
|
||||
List<Role> roles = [];
|
||||
List<User> users = [];
|
||||
|
||||
String? imageName;
|
||||
Uint8List? webImage;
|
||||
|
||||
//PANTALLA USUARIOS
|
||||
final busquedaController = TextEditingController();
|
||||
String orden = "sequential_id";
|
||||
|
||||
Future<void> updateState() async {
|
||||
busquedaController.clear();
|
||||
await getRoles(notify: false);
|
||||
await getUsers();
|
||||
}
|
||||
|
||||
void clearControllers({bool clearEmail = true, bool notify = true}) {
|
||||
nameController.clear();
|
||||
if (clearEmail) emailController.clear();
|
||||
lastNameController.clear();
|
||||
phoneController.clear();
|
||||
selectedRole = null;
|
||||
|
||||
imageName = null;
|
||||
webImage = null;
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
void setSelectedRole(String role) async {
|
||||
selectedRole = roles.firstWhere((e) => e.name == role);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getRoles({bool notify = true}) async {
|
||||
if (roles.isNotEmpty) return;
|
||||
final res = await supabase
|
||||
.from('role')
|
||||
.select()
|
||||
.eq('organization_fk', organizationId)
|
||||
.order('name', ascending: true);
|
||||
|
||||
roles = (res as List<dynamic>).map((role) => Role.fromMap(role)).toList();
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getUsers() async {
|
||||
try {
|
||||
final query =
|
||||
supabase.from('users').select().eq('organization_id', organizationId);
|
||||
|
||||
final res = await query
|
||||
.like('first_name', '%${busquedaController.text}%')
|
||||
.order(orden, ascending: true);
|
||||
|
||||
if (res == null) {
|
||||
log('Error in getUsers()');
|
||||
return;
|
||||
}
|
||||
|
||||
users = (res as List<dynamic>).map((user) => User.fromMap(user)).toList();
|
||||
|
||||
fillPlutoGrid(users);
|
||||
} catch (e) {
|
||||
log('Error in getUsers() - $e');
|
||||
}
|
||||
}
|
||||
|
||||
void fillPlutoGrid(List<User> users) {
|
||||
rows.clear();
|
||||
for (User user in users) {
|
||||
rows.add(
|
||||
PlutoRow(
|
||||
cells: {
|
||||
'sequential_id': PlutoCell(value: user.sequentialId),
|
||||
'full_name': PlutoCell(value: user.fullName),
|
||||
'role': PlutoCell(value: user.role.name),
|
||||
'email': PlutoCell(value: user.email),
|
||||
'mobile_phone': PlutoCell(value: user.mobilePhone ?? '-'),
|
||||
'status': PlutoCell(value: user),
|
||||
'actions': PlutoCell(value: user),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
if (stateManager != null) stateManager!.notifyListeners();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> selectImage() async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
final XFile? pickedImage = await picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
);
|
||||
|
||||
if (pickedImage == null) return;
|
||||
|
||||
final String fileExtension = p.extension(pickedImage.name);
|
||||
const uuid = Uuid();
|
||||
final String fileName = uuid.v1();
|
||||
imageName = 'avatar-$fileName$fileExtension';
|
||||
|
||||
webImage = await pickedImage.readAsBytes();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearImage() {
|
||||
webImage = null;
|
||||
imageName = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<String?> uploadImage() async {
|
||||
if (webImage != null && imageName != null) {
|
||||
await supabase.storage.from('avatars').uploadBinary(
|
||||
imageName!,
|
||||
webImage!,
|
||||
fileOptions: const FileOptions(
|
||||
cacheControl: '3600',
|
||||
upsert: false,
|
||||
),
|
||||
);
|
||||
|
||||
return imageName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> validateImage(String? imagen) async {
|
||||
if (imagen == null) {
|
||||
if (webImage != null) {
|
||||
//usuario no tiene imagen y se agrego => se sube imagen
|
||||
final res = await uploadImage();
|
||||
if (res == null) {
|
||||
ApiErrorHandler.callToast('Error uploading image');
|
||||
}
|
||||
}
|
||||
//usuario no tiene imagen y no se agrego => no hace nada
|
||||
} else {
|
||||
//usuario tiene imagen y se borro => se borra en bd
|
||||
if (webImage == null && imageName == null) {
|
||||
await supabase.storage.from('avatars').remove([imagen]);
|
||||
}
|
||||
//usuario tiene imagen y no se modifico => no se hace nada
|
||||
|
||||
//usuario tiene imagen y se cambio => se borra en bd y se sube la nueva
|
||||
if (webImage != null && imageName != imagen) {
|
||||
await supabase.storage.from('avatars').remove([imagen]);
|
||||
final res2 = await uploadImage();
|
||||
if (res2 == null) {
|
||||
ApiErrorHandler.callToast('Error uploading image');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, String>?> registerUser() async {
|
||||
try {
|
||||
//Generar contrasena aleatoria
|
||||
// final password = generatePassword();
|
||||
|
||||
//Registrar al usuario con una contraseña temporal
|
||||
var response = await http.post(
|
||||
Uri.parse('$supabaseUrl/auth/v1/signup'),
|
||||
headers: {'Content-Type': 'application/json', 'apiKey': anonKey},
|
||||
body: json.encode(
|
||||
{
|
||||
"email": emailController.text,
|
||||
"password": 'default',
|
||||
},
|
||||
),
|
||||
);
|
||||
if (response.statusCode > 204)
|
||||
return {'Error': 'The user already exists'};
|
||||
|
||||
final String? userId = jsonDecode(response.body)['user']['id'];
|
||||
|
||||
if (userId == null) return {'Error': 'Could not register user'};
|
||||
|
||||
// final token = generateToken(userId, correoController.text);
|
||||
|
||||
// final bool tokenSaved = await SupabaseQueries.saveToken(userId, 'token_ingreso', token);
|
||||
|
||||
// if (!tokenSaved) return {'Error': 'Error al guardar token'};
|
||||
|
||||
// final bool emailSent = await sendEmail(correoController.text, password, token, 'alta');
|
||||
|
||||
// if (!emailSent) return {'Error': 'Error al mandar email'};
|
||||
|
||||
//retornar el id del usuario
|
||||
return {'userId': userId};
|
||||
} catch (e) {
|
||||
log('Error en registerUser() - $e');
|
||||
return {'Error': 'Could not register user'};
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> createUserProfile(String userId) async {
|
||||
if (selectedRole == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await supabase.from('user_profile').insert(
|
||||
{
|
||||
'user_profile_id': userId,
|
||||
'first_name': nameController.text,
|
||||
'last_name': lastNameController.text,
|
||||
'mobile_phone': phoneController.text,
|
||||
'role_fk': selectedRole!.roleId,
|
||||
'image': imageName,
|
||||
'organization_fk': organizationId,
|
||||
},
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error in createUserProfile() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> editUserProfile(String userId) async {
|
||||
try {
|
||||
await supabase.from('user_profile').update(
|
||||
{
|
||||
'first_name': nameController.text,
|
||||
'last_name': lastNameController.text,
|
||||
'mobile_phone': phoneController.text,
|
||||
'role_fk': selectedRole!.roleId,
|
||||
'image': imageName,
|
||||
},
|
||||
).eq('user_profile_id', userId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('Error in editUserProfile() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initEditUser(User user) async {
|
||||
nameController.text = user.firstName;
|
||||
lastNameController.text = user.lastName;
|
||||
emailController.text = user.email;
|
||||
phoneController.text = user.mobilePhone ?? '';
|
||||
selectedRole = user.role;
|
||||
imageName = user.image;
|
||||
webImage = null;
|
||||
}
|
||||
|
||||
// Future<bool> updateActivado(User usuario, bool value, int rowIndex) async {
|
||||
// try {
|
||||
// //actualizar usuario
|
||||
// await supabase.from('perfil_usuario').update({'activo': value}).eq('perfil_usuario_id', usuario.id);
|
||||
// rows[rowIndex].cells['activo']?.value = usuario.estatus;
|
||||
// if (stateManager != null) stateManager!.notifyListeners();
|
||||
// return true;
|
||||
// } catch (e) {
|
||||
// log('Error en updateActivado() - $e');
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
String generatePassword() {
|
||||
//Generar contrasena aleatoria
|
||||
final passwordGenerator = RandomPasswordGenerator();
|
||||
return passwordGenerator.randomPassword(
|
||||
letters: true,
|
||||
uppercase: true,
|
||||
numbers: true,
|
||||
specialChar: true,
|
||||
passwordLength: 8,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> sendEmail(
|
||||
String email, String? password, String token, String type) async {
|
||||
//Mandar correo
|
||||
// final response = await http.post(
|
||||
// Uri.parse(bonitaConnectionUrl),
|
||||
// body: json.encode(
|
||||
// {
|
||||
// "user": "Web",
|
||||
// "action": "bonitaBpmCaseVariables",
|
||||
// 'process': 'Alta_de_Usuario',
|
||||
// 'data': {
|
||||
// 'variables': [
|
||||
// {
|
||||
// 'name': 'correo',
|
||||
// 'value': email,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'password',
|
||||
// 'value': password,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'token',
|
||||
// 'value': token,
|
||||
// },
|
||||
// {
|
||||
// 'name': 'type',
|
||||
// 'value': type,
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// if (response.statusCode > 204) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> borrarUsuario(String userId) async {
|
||||
try {
|
||||
final res = await supabase.rpc('borrar_usuario_id', params: {
|
||||
'user_id': userId,
|
||||
});
|
||||
users.removeWhere((user) => user.id == userId);
|
||||
fillPlutoGrid(users);
|
||||
return res;
|
||||
} catch (e) {
|
||||
log('Error en borrarUsuario() - $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
busquedaController.dispose();
|
||||
nameController.dispose();
|
||||
emailController.dispose();
|
||||
lastNameController.dispose();
|
||||
phoneController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
184
lib/providers/visual_state_provider.dart
Normal file
184
lib/providers/visual_state_provider.dart
Normal file
@@ -0,0 +1,184 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_side_menu/flutter_side_menu.dart';
|
||||
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class VisualStateProvider extends ChangeNotifier {
|
||||
List<bool> isTaped = [
|
||||
true, //Administrador contenido lu 0
|
||||
false, //CRM 1
|
||||
false, //Customers 2
|
||||
false, //QR 3
|
||||
false, //Usuarios 4
|
||||
false, //Administrador contenido videos 5
|
||||
false, //Administrador cupones 6
|
||||
false //Inventario 7
|
||||
];
|
||||
|
||||
//THEME
|
||||
late Color primaryColorLight;
|
||||
late Color secondaryColorLight;
|
||||
late Color tertiaryColorLight;
|
||||
late Color primaryTextColorLight;
|
||||
late Color primaryBackgroundColorLight;
|
||||
|
||||
late Color primaryColorDark;
|
||||
late Color secondaryColorDark;
|
||||
late Color tertiaryColorDark;
|
||||
late Color primaryTextColorDark;
|
||||
late Color primaryBackgroundColorDark;
|
||||
|
||||
late TextEditingController primaryColorLightController;
|
||||
late TextEditingController secondaryColorLightController;
|
||||
late TextEditingController tertiaryColorLightController;
|
||||
late TextEditingController primaryTextLightController;
|
||||
late TextEditingController primaryBackgroundLightController;
|
||||
|
||||
late TextEditingController primaryColorDarkController;
|
||||
late TextEditingController secondaryColorDarkController;
|
||||
late TextEditingController tertiaryColorDarkController;
|
||||
late TextEditingController primaryTextDarkController;
|
||||
late TextEditingController primaryBackgroundDarkController;
|
||||
|
||||
//nombreTema
|
||||
TextEditingController nombreTema = TextEditingController();
|
||||
|
||||
//SideMenu
|
||||
SideMenuController sideMenuController = SideMenuController();
|
||||
|
||||
VisualStateProvider(BuildContext context) {
|
||||
final lightTheme = AppTheme.lightTheme;
|
||||
final darkTheme = AppTheme.darkTheme;
|
||||
|
||||
primaryColorLight = lightTheme.primaryColor;
|
||||
secondaryColorLight = lightTheme.secondaryColor;
|
||||
tertiaryColorLight = lightTheme.tertiaryColor;
|
||||
primaryTextColorLight = lightTheme.primaryText;
|
||||
primaryBackgroundColorLight = lightTheme.primaryBackground;
|
||||
|
||||
primaryColorDark = darkTheme.primaryColor;
|
||||
secondaryColorDark = darkTheme.secondaryColor;
|
||||
tertiaryColorDark = darkTheme.tertiaryColor;
|
||||
primaryTextColorDark = darkTheme.primaryText;
|
||||
primaryBackgroundColorDark = darkTheme.primaryBackground;
|
||||
|
||||
primaryColorLightController = TextEditingController(
|
||||
text: primaryColorLight.value.toRadixString(16).toUpperCase());
|
||||
secondaryColorLightController = TextEditingController(
|
||||
text: secondaryColorLight.value.toRadixString(16).toUpperCase());
|
||||
tertiaryColorLightController = TextEditingController(
|
||||
text: tertiaryColorLight.value.toRadixString(16).toUpperCase());
|
||||
primaryTextLightController = TextEditingController(
|
||||
text: primaryTextColorLight.value.toRadixString(16).toUpperCase());
|
||||
primaryBackgroundLightController = TextEditingController(
|
||||
text:
|
||||
primaryBackgroundColorLight.value.toRadixString(16).toUpperCase());
|
||||
|
||||
primaryColorDarkController = TextEditingController(
|
||||
text: primaryColorDark.value.toRadixString(16).toUpperCase());
|
||||
secondaryColorDarkController = TextEditingController(
|
||||
text: secondaryColorDark.value.toRadixString(16).toUpperCase());
|
||||
tertiaryColorDarkController = TextEditingController(
|
||||
text: tertiaryColorDark.value.toRadixString(16).toUpperCase());
|
||||
primaryTextDarkController = TextEditingController(
|
||||
text: primaryTextColorDark.value.toRadixString(16).toUpperCase());
|
||||
primaryBackgroundDarkController = TextEditingController(
|
||||
text: primaryBackgroundColorDark.value.toRadixString(16).toUpperCase());
|
||||
}
|
||||
|
||||
void setPrimaryColorLight(Color color) {
|
||||
primaryColorLight = color;
|
||||
primaryColorLightController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSecondaryColorLight(Color color) {
|
||||
secondaryColorLight = color;
|
||||
secondaryColorLightController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTerciaryColorLight(Color color) {
|
||||
tertiaryColorLight = color;
|
||||
tertiaryColorLightController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPrimaryTextColorLight(Color color) {
|
||||
primaryTextColorLight = color;
|
||||
primaryTextLightController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPrimaryBackgroundColorLight(Color color) {
|
||||
primaryBackgroundColorLight = color;
|
||||
primaryBackgroundLightController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPrimaryColorDark(Color color) {
|
||||
primaryColorDark = color;
|
||||
primaryColorDarkController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSecondaryColorDark(Color color) {
|
||||
secondaryColorDark = color;
|
||||
secondaryColorDarkController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTerciaryColorDark(Color color) {
|
||||
tertiaryColorDark = color;
|
||||
tertiaryColorDarkController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPrimaryTextColorDark(Color color) {
|
||||
primaryTextColorDark = color;
|
||||
primaryTextDarkController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPrimaryBackgroundColorDark(Color color) {
|
||||
primaryBackgroundColorDark = color;
|
||||
primaryBackgroundDarkController.text =
|
||||
color.value.toRadixString(16).toUpperCase();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void toggleSideMenu() {
|
||||
sideMenuController.toggle();
|
||||
}
|
||||
|
||||
void setTapedOption(int index) {
|
||||
for (var i = 0; i < isTaped.length; i++) {
|
||||
isTaped[i] = false;
|
||||
}
|
||||
isTaped[index] = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
primaryColorLightController.dispose();
|
||||
secondaryColorLightController.dispose();
|
||||
tertiaryColorLightController.dispose();
|
||||
primaryTextLightController.dispose();
|
||||
primaryBackgroundLightController.dispose();
|
||||
primaryColorDarkController.dispose();
|
||||
secondaryColorDarkController.dispose();
|
||||
tertiaryColorDarkController.dispose();
|
||||
primaryTextDarkController.dispose();
|
||||
primaryBackgroundDarkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
65
lib/router/router.dart
Normal file
65
lib/router/router.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:nethive_neo/functions/no_transition_route.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
|
||||
import 'package:nethive_neo/pages/pages.dart';
|
||||
|
||||
import 'package:nethive_neo/services/navigation_service.dart';
|
||||
|
||||
/// The route configuration.
|
||||
final GoRouter router = GoRouter(
|
||||
debugLogDiagnostics: true,
|
||||
navigatorKey: NavigationService.navigatorKey,
|
||||
initialLocation: '/',
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
final bool loggedIn = currentUser != null;
|
||||
final bool isLoggingIn = state.matchedLocation.contains('/login');
|
||||
|
||||
// If user is not logged in and not in the login page
|
||||
if (!loggedIn && !isLoggingIn) return '/login';
|
||||
|
||||
//if user is logged in and in the login page
|
||||
if (loggedIn && isLoggingIn) {
|
||||
if (currentUser!.role.roleId == 14 || currentUser!.role.roleId == 13) {
|
||||
return '/book_page_main';
|
||||
} else {
|
||||
return '/';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
errorBuilder: (context, state) => const PageNotFoundPage(),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
name: 'root',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
if (currentUser!.role.roleId == 14 || currentUser!.role.roleId == 13) {
|
||||
return Container(
|
||||
color: Colors.amber,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: const Center(child: Text('Book Page Main')));
|
||||
} else {
|
||||
return Container(
|
||||
color: Color.fromARGB(255, 7, 27, 180),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: const Center(child: Text('Book Page Main')));
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
33
lib/services/api_error_handler.dart
Normal file
33
lib/services/api_error_handler.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
class ApiErrorHandler {
|
||||
static String translateErrorMsg(String message) {
|
||||
switch (message) {
|
||||
//Login
|
||||
case 'Invalid login credentials':
|
||||
return 'Credenciales inválidas';
|
||||
//Reset Password
|
||||
case 'User not found':
|
||||
return 'Usuario no encontrado';
|
||||
case 'For security purposes, you can only request this once every 60 seconds':
|
||||
return 'Solo se puede solicitar este recurso una vez por minuto';
|
||||
default:
|
||||
return 'Error al realizar petición';
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> callToast([
|
||||
String msg = 'There was an error after sending request',
|
||||
String color = "#e74c3c",
|
||||
]) async {
|
||||
await Fluttertoast.showToast(
|
||||
msg: msg,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
webBgColor: color,
|
||||
textColor: Colors.black,
|
||||
timeInSecForIosWeb: 5,
|
||||
webPosition: 'center',
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/services/navigation_service.dart
Normal file
18
lib/services/navigation_service.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavigationService {
|
||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
static navigateTo(String routeName) async {
|
||||
return await navigatorKey.currentState!.pushNamed(routeName);
|
||||
}
|
||||
|
||||
static replaceTo(String routeName) async {
|
||||
return await navigatorKey.currentState!.pushReplacementNamed(routeName);
|
||||
}
|
||||
|
||||
static removeTo(String newRoute) async {
|
||||
return await navigatorKey.currentState!
|
||||
.pushNamedAndRemoveUntil(newRoute, (_) => false);
|
||||
}
|
||||
}
|
||||
350
lib/theme/theme.dart
Normal file
350
lib/theme/theme.dart
Normal file
@@ -0,0 +1,350 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/main.dart';
|
||||
import 'package:nethive_neo/models/configuration.dart';
|
||||
|
||||
const kThemeModeKey = '__theme_mode__';
|
||||
|
||||
void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
|
||||
MyApp.of(context).setThemeMode(themeMode);
|
||||
|
||||
abstract class AppTheme {
|
||||
static ThemeMode get themeMode {
|
||||
final darkMode = prefs.getBool(kThemeModeKey);
|
||||
return darkMode == null
|
||||
? ThemeMode.light
|
||||
: darkMode
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light;
|
||||
}
|
||||
|
||||
static LightModeTheme lightTheme = LightModeTheme();
|
||||
static DarkModeTheme darkTheme = DarkModeTheme();
|
||||
|
||||
static void initConfiguration(Configuration? conf) {
|
||||
lightTheme = LightModeTheme(mode: conf?.config!.light);
|
||||
darkTheme = DarkModeTheme(mode: conf?.config!.dark);
|
||||
}
|
||||
|
||||
static void saveThemeMode(ThemeMode mode) => mode == ThemeMode.system
|
||||
? prefs.remove(kThemeModeKey)
|
||||
: prefs.setBool(kThemeModeKey, mode == ThemeMode.dark);
|
||||
|
||||
static AppTheme of(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark ? darkTheme : lightTheme;
|
||||
Color hexToColor(String hexString) {
|
||||
// Quita el signo de almohadilla si está presente
|
||||
hexString = hexString.toUpperCase().replaceAll("#",
|
||||
""); // Si la cadena tiene 6 caracteres, añade el valor de opacidad completa "FF"
|
||||
if (hexString.length == 6) {
|
||||
hexString = "FF$hexString";
|
||||
} // Si la cadena tiene 8 caracteres, ya incluye la transparencia, así que se deja como está
|
||||
else if (hexString.length == 8) {
|
||||
hexString = hexString.substring(6, 8) + hexString.substring(0, 6);
|
||||
} // Añade el prefijo 0x y convierte la cadena hexadecimal a un entero
|
||||
return Color(int.parse("0x$hexString"));
|
||||
}
|
||||
|
||||
abstract Color primaryColor;
|
||||
abstract Color secondaryColor;
|
||||
abstract Color tertiaryColor;
|
||||
abstract Color alternate;
|
||||
abstract Color primaryBackground;
|
||||
abstract Color secondaryBackground;
|
||||
abstract Color tertiaryBackground;
|
||||
abstract Color transparentBackground;
|
||||
abstract Color primaryText;
|
||||
abstract Color secondaryText;
|
||||
abstract Color tertiaryText;
|
||||
abstract Color hintText;
|
||||
abstract Color error;
|
||||
abstract Color warning;
|
||||
abstract Color success;
|
||||
abstract Color formBackground;
|
||||
|
||||
Gradient blueGradient = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment(4, 0.8),
|
||||
colors: <Color>[
|
||||
Color(0xFF0090FF),
|
||||
Color(0xFF0363C8),
|
||||
Color(0xFF063E9B),
|
||||
Color(0xFF0A0859),
|
||||
],
|
||||
);
|
||||
|
||||
Gradient primaryGradient = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment(4, 0.8),
|
||||
colors: <Color>[
|
||||
Color(0xFF6C5DD3),
|
||||
Color(0xFF090046),
|
||||
Color(0xFF05002A),
|
||||
],
|
||||
);
|
||||
|
||||
String get title1Family => typography.title1Family;
|
||||
TextStyle get title1 => typography.title1;
|
||||
String get title2Family => typography.title2Family;
|
||||
TextStyle get title2 => typography.title2;
|
||||
String get title3Family => typography.title3Family;
|
||||
TextStyle get title3 => typography.title3;
|
||||
String get subtitle1Family => typography.subtitle1Family;
|
||||
TextStyle get subtitle1 => typography.subtitle1;
|
||||
String get subtitle2Family => typography.subtitle2Family;
|
||||
TextStyle get subtitle2 => typography.subtitle2;
|
||||
String get bodyText1Family => typography.bodyText1Family;
|
||||
TextStyle get bodyText1 => typography.bodyText1;
|
||||
String get bodyText2Family => typography.bodyText2Family;
|
||||
TextStyle get bodyText2 => typography.bodyText2;
|
||||
String get bodyText3Family => typography.bodyText3Family;
|
||||
TextStyle get bodyText3 => typography.bodyText3;
|
||||
String get plutoDataTextFamily => typography.plutoDataTextFamily;
|
||||
TextStyle get plutoDataText => typography.plutoDataText;
|
||||
String get copyRightTextFamily => typography.copyRightTextFamily;
|
||||
TextStyle get copyRightText => typography.copyRightText;
|
||||
|
||||
Typography get typography => ThemeTypography(this);
|
||||
}
|
||||
|
||||
class LightModeTheme extends AppTheme {
|
||||
@override
|
||||
Color primaryColor = const Color(0xFF663DD9);
|
||||
@override
|
||||
Color secondaryColor = const Color(0xFF12142D);
|
||||
@override
|
||||
Color tertiaryColor = const Color(0xFF14B095);
|
||||
@override
|
||||
Color alternate = const Color(0xFFFECE05);
|
||||
@override
|
||||
Color primaryBackground = const Color(0xFFFFFFFF);
|
||||
@override
|
||||
Color secondaryBackground = const Color(0xFFF9F9F9);
|
||||
@override
|
||||
Color tertiaryBackground = const Color(0XFFF1F0F0);
|
||||
@override
|
||||
Color transparentBackground = const Color(0XFF4D4D4D).withOpacity(.2);
|
||||
@override
|
||||
Color primaryText = const Color(0xFF12142D);
|
||||
@override
|
||||
Color secondaryText = const Color(0XFF000000);
|
||||
@override
|
||||
Color tertiaryText = const Color(0XFF747474);
|
||||
@override
|
||||
Color hintText = const Color(0XFF8A88A0);
|
||||
@override
|
||||
Color error = const Color(0XFFF44A49);
|
||||
@override
|
||||
Color warning = const Color(0XFFF5AB1A);
|
||||
@override
|
||||
Color success = const Color(0XFF3AC170);
|
||||
@override
|
||||
Color formBackground = const Color(0xFF663DD9).withOpacity(.2);
|
||||
|
||||
LightModeTheme({Mode? mode}) {
|
||||
if (mode != null) {
|
||||
primaryColor = hexToColor(mode.primaryColor!);
|
||||
secondaryColor = hexToColor(mode.secondaryColor!);
|
||||
tertiaryColor = hexToColor(mode.tertiaryColor!);
|
||||
primaryText = hexToColor(mode.primaryText!);
|
||||
primaryBackground = hexToColor(mode.primaryBackground!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DarkModeTheme extends AppTheme {
|
||||
@override
|
||||
Color primaryColor = const Color(0xFF6C5DD3);
|
||||
@override
|
||||
Color secondaryColor = const Color(0xFF098BF7);
|
||||
@override
|
||||
Color tertiaryColor = const Color(0xFF14B095);
|
||||
@override
|
||||
Color alternate = const Color(0xFFFECE05);
|
||||
@override
|
||||
Color primaryBackground = Color(0xFF292929);
|
||||
@override
|
||||
Color secondaryBackground = Color(0xFF414141);
|
||||
@override
|
||||
Color tertiaryBackground = Color(0xFF343434);
|
||||
@override
|
||||
Color transparentBackground = const Color(0XFF4D4D4D).withOpacity(.2);
|
||||
@override
|
||||
Color primaryText = Color(0xFFD7D7D7);
|
||||
@override
|
||||
Color secondaryText = Color(0xFF5C63C8);
|
||||
@override
|
||||
Color tertiaryText = const Color(0XFF747474);
|
||||
@override
|
||||
Color hintText = const Color(0XFF8A88A0);
|
||||
@override
|
||||
Color error = const Color(0XFFF44A49);
|
||||
@override
|
||||
Color warning = const Color(0XFFF5AB1A);
|
||||
@override
|
||||
Color success = const Color(0XFF3AC170);
|
||||
@override
|
||||
Color formBackground = const Color(0xFF663DD9).withOpacity(.2);
|
||||
|
||||
DarkModeTheme({Mode? mode}) {
|
||||
if (mode != null) {
|
||||
primaryColor = hexToColor(mode.primaryColor!);
|
||||
secondaryColor = hexToColor(mode.secondaryColor!);
|
||||
tertiaryColor = hexToColor(mode.tertiaryColor!);
|
||||
primaryText = hexToColor(mode.primaryText!);
|
||||
primaryBackground = hexToColor(mode.primaryBackground!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TextStyleHelper on TextStyle {
|
||||
TextStyle override({
|
||||
required String fontFamily,
|
||||
Color? color,
|
||||
double? fontSize,
|
||||
FontWeight? fontWeight,
|
||||
FontStyle? fontStyle,
|
||||
double? letterSpacing,
|
||||
bool useGoogleFonts = true,
|
||||
TextDecoration? decoration,
|
||||
TextDecorationStyle? decorationStyle,
|
||||
double? lineHeight,
|
||||
}) =>
|
||||
useGoogleFonts
|
||||
? GoogleFonts.getFont(
|
||||
fontFamily,
|
||||
color: color ?? this.color,
|
||||
fontSize: fontSize ?? this.fontSize,
|
||||
fontWeight: fontWeight ?? this.fontWeight,
|
||||
fontStyle: fontStyle ?? this.fontStyle,
|
||||
letterSpacing: letterSpacing ?? this.letterSpacing,
|
||||
decoration: decoration,
|
||||
decorationStyle: decorationStyle,
|
||||
height: lineHeight,
|
||||
)
|
||||
: copyWith(
|
||||
fontFamily: fontFamily,
|
||||
color: color,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
letterSpacing: letterSpacing,
|
||||
fontStyle: fontStyle,
|
||||
decoration: decoration,
|
||||
decorationStyle: decorationStyle,
|
||||
height: lineHeight,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class Typography {
|
||||
String get title1Family;
|
||||
TextStyle get title1;
|
||||
String get title2Family;
|
||||
TextStyle get title2;
|
||||
String get title3Family;
|
||||
TextStyle get title3;
|
||||
String get subtitle1Family;
|
||||
TextStyle get subtitle1;
|
||||
String get subtitle2Family;
|
||||
TextStyle get subtitle2;
|
||||
String get bodyText1Family;
|
||||
TextStyle get bodyText1;
|
||||
String get bodyText2Family;
|
||||
TextStyle get bodyText2;
|
||||
String get bodyText3Family;
|
||||
TextStyle get bodyText3;
|
||||
String get plutoDataTextFamily;
|
||||
TextStyle get plutoDataText;
|
||||
String get copyRightTextFamily;
|
||||
TextStyle get copyRightText;
|
||||
}
|
||||
|
||||
class ThemeTypography extends Typography {
|
||||
ThemeTypography(this.theme);
|
||||
|
||||
final AppTheme theme;
|
||||
|
||||
@override
|
||||
String get title1Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get title1 => GoogleFonts.poppins(
|
||||
fontSize: 70,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
@override
|
||||
String get title2Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get title2 => GoogleFonts.poppins(
|
||||
fontSize: 65,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
@override
|
||||
String get title3Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get title3 => GoogleFonts.poppins(
|
||||
fontSize: 48,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
|
||||
@override
|
||||
String get subtitle1Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get subtitle1 => GoogleFonts.poppins(
|
||||
fontSize: 28,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
@override
|
||||
String get subtitle2Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get subtitle2 => GoogleFonts.poppins(
|
||||
fontSize: 24,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
|
||||
@override
|
||||
String get bodyText1Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get bodyText1 => GoogleFonts.poppins(
|
||||
fontSize: 20,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
);
|
||||
@override
|
||||
String get bodyText2Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get bodyText2 => GoogleFonts.poppins(
|
||||
fontSize: 18,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
);
|
||||
@override
|
||||
String get bodyText3Family => 'Poppins';
|
||||
@override
|
||||
TextStyle get bodyText3 => GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
);
|
||||
@override
|
||||
String get plutoDataTextFamily => 'Poppins';
|
||||
@override
|
||||
TextStyle get plutoDataText => GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
);
|
||||
@override
|
||||
String get copyRightTextFamily => 'Poppins';
|
||||
@override
|
||||
TextStyle get copyRightText => GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user