diff --git a/lib/config/router.dart b/lib/config/routes/router.dart similarity index 81% rename from lib/config/router.dart rename to lib/config/routes/router.dart index 2826c2b..52b83c3 100644 --- a/lib/config/router.dart +++ b/lib/config/routes/router.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:get/route_manager.dart'; -import 'package:mi_utem/config/routes.dart'; -import 'package:mi_utem/screens/asignatura_detalle_screen.dart'; -import 'package:mi_utem/screens/asignaturas_lista_screen.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/controllers/asignaturas_controller.dart'; import 'package:mi_utem/controllers/qr_passes_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; +import 'package:mi_utem/screens/asignatura/asignatura_detalle_screen.dart'; +import 'package:mi_utem/screens/asignatura/asignaturas_lista_screen.dart'; import 'package:mi_utem/screens/calculadora_notas_screen.dart'; import 'package:mi_utem/screens/credencial_screen.dart'; import 'package:mi_utem/screens/horario/horario_screen.dart'; import 'package:mi_utem/screens/login_screen/login_screen.dart'; -import 'package:mi_utem/screens/main_screen.dart'; +import 'package:mi_utem/screens/main/main_screen.dart'; import 'package:mi_utem/screens/permiso_covid_screen.dart'; import 'package:mi_utem/screens/splash_screen.dart'; import 'package:mi_utem/screens/usuario_screen.dart'; -import 'package:mi_utem/services/auth_service.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/widgets/acerca_screen.dart'; final _loginPage = GetPage( @@ -26,11 +25,7 @@ final _loginPage = GetPage( final _homePage = GetPage( name: Routes.home, bindings: [QrPassesBinding()], - page: () { - final usuario = PerfilService.getLocalUsuario(); - - return MainScreen(usuario: usuario); - }, + page: () => MainScreen(), middlewares: [OnlyAuthMiddleware()], ); @@ -86,7 +81,7 @@ final pages = [ class OnlyAuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? page) { - final isLoggedIn = AuthService.isLoggedIn(); + final isLoggedIn = UserController.to.isLoggedIn; if (!isLoggedIn) { return const RouteSettings(name: Routes.login); } @@ -97,7 +92,7 @@ class OnlyAuthMiddleware extends GetMiddleware { class OnlyNoAuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? page) { - final isLoggedIn = AuthService.isLoggedIn(); + final isLoggedIn = UserController.to.isLoggedIn; if (isLoggedIn) { return const RouteSettings(name: Routes.home); } diff --git a/lib/config/routes.dart b/lib/config/routes/routes.dart similarity index 100% rename from lib/config/routes.dart rename to lib/config/routes/routes.dart diff --git a/lib/controllers/asignatura_controller.dart b/lib/controllers/asignatura_controller.dart index 544e732..17a9e1e 100644 --- a/lib/controllers/asignatura_controller.dart +++ b/lib/controllers/asignatura_controller.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; -import 'package:mi_utem/controllers/carreras_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/models/carrera.dart'; import 'package:mi_utem/models/grades.dart'; @@ -19,13 +19,13 @@ class AsignaturaController extends GetxController with StateMixin { @override void onInit() { - _selectedCarrera = Get.find().selectedCarrera.value; + _selectedCarrera = Get.find().selectedCarrera.value; if (_selectedCarrera != null) { - getAsignaturaDetail(Get.find().selectedCarrera.value); + getAsignaturaDetail(Get.find().selectedCarrera.value); } ever( - Get.find().selectedCarrera, + Get.find().selectedCarrera, (carrera) { _selectedCarrera = carrera; getAsignaturaDetail(carrera, forceRefresh: true); diff --git a/lib/controllers/asignaturas_controller.dart b/lib/controllers/asignaturas_controller.dart index a8090d1..cf12a3f 100644 --- a/lib/controllers/asignaturas_controller.dart +++ b/lib/controllers/asignaturas_controller.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; import 'package:mi_utem/controllers/asignatura_controller.dart'; -import 'package:mi_utem/controllers/carreras_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/models/carrera.dart'; import 'package:mi_utem/services/asignaturas_service.dart'; @@ -11,12 +11,12 @@ class AsignaturasController extends GetxController void onInit() { change(null, status: RxStatus.loading()); - if (Get.find().selectedCarrera.value != null) { - getAsignaturas(Get.find().selectedCarrera.value); + if (Get.find().selectedCarrera.value != null) { + getAsignaturas(Get.find().selectedCarrera.value); } ever( - Get.find().selectedCarrera, + Get.find().selectedCarrera, (carrera) => getAsignaturas(carrera, forceRefresh: true), ); super.onInit(); @@ -54,7 +54,7 @@ class AsignaturasController extends GetxController } void refreshAsignaturas() { - final carrera = Get.find().selectedCarrera.value; + final carrera = Get.find().selectedCarrera.value; getAsignaturas(carrera, forceRefresh: true); } } diff --git a/lib/controllers/carreras_controller.dart b/lib/controllers/carreras_controller.dart index 20eb963..c8d6aa6 100644 --- a/lib/controllers/carreras_controller.dart +++ b/lib/controllers/carreras_controller.dart @@ -1,25 +1,31 @@ import 'package:get/get.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/carrera.dart'; +import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/analytics_service.dart'; import 'package:mi_utem/services/carreras_service.dart'; -class CarrerasController extends GetxController { - final carreras = [].obs; - final selectedCarrera = Rxn(); - +class CarrerasController extends GetxController with StateMixin> { static CarrerasController get to => Get.find(); @override void onInit() { getCarreras(); + ever( + Get.find().user, + (carrera) => getCarreras(), + ); + super.onInit(); } void getCarreras() async { + change(null, status: RxStatus.loading()); + final carreras = await CarreraService.getCarreras(forceRefresh: true); - this.carreras.value = carreras; + change(carreras, status: RxStatus.success()); _autoSelectCarreraActiva(carreras); } @@ -43,6 +49,6 @@ class CarrerasController extends GetxController { } void changeSelectedCarrera(Carrera carrera) { - selectedCarrera.value = carrera; + UserController.to.selectCarrera(carrera); } } diff --git a/lib/controllers/grades_changes_controller.dart b/lib/controllers/grades_changes_controller.dart index 0be8fb7..0c81836 100644 --- a/lib/controllers/grades_changes_controller.dart +++ b/lib/controllers/grades_changes_controller.dart @@ -1,11 +1,10 @@ import 'dart:developer'; import 'package:get_storage/get_storage.dart'; -import 'package:mi_utem/controllers/carreras_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/models/grades.dart'; import 'package:mi_utem/services/asignaturas_service.dart'; -import 'package:mi_utem/services/auth_service.dart'; import 'package:mi_utem/services/grades_service.dart'; import 'package:mi_utem/services/notification_service.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -141,10 +140,10 @@ class GradesChangesController { } static Future> checkIfGradesHasChange() async { - final isLogged = AuthService.isLoggedIn(); + final isLogged = UserController.to.isLoggedIn; if (isLogged) { - final carrera = CarrerasController.to.selectedCarrera.value; + final carrera = UserController.to.selectedCarrera.value; final carreraId = carrera?.id; if (carreraId != null) { diff --git a/lib/controllers/horario_controller.dart b/lib/controllers/horario_controller.dart index aac0bb3..02cdb85 100644 --- a/lib/controllers/horario_controller.dart +++ b/lib/controllers/horario_controller.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:mi_utem/controllers/carreras_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/models/carrera.dart'; import 'package:mi_utem/models/horario.dart'; @@ -85,12 +85,12 @@ class HorarioController extends GetxController { @override onInit() { - if (Get.find().selectedCarrera.value != null) { - getHorarioData(Get.find().selectedCarrera.value); + if (Get.find().selectedCarrera.value != null) { + getHorarioData(Get.find().selectedCarrera.value); } ever( - Get.find().selectedCarrera, + Get.find().selectedCarrera, (carrera) => getHorarioData(carrera), ); _init(); diff --git a/lib/controllers/notification_controller.dart b/lib/controllers/notification_controller.dart index 7e2950f..b4bc4c1 100644 --- a/lib/controllers/notification_controller.dart +++ b/lib/controllers/notification_controller.dart @@ -5,7 +5,7 @@ import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:awesome_notifications_fcm/awesome_notifications_fcm.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/services/analytics_service.dart'; diff --git a/lib/controllers/user_controller.dart b/lib/controllers/user_controller.dart new file mode 100644 index 0000000..61c4c3c --- /dev/null +++ b/lib/controllers/user_controller.dart @@ -0,0 +1,154 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:mi_utem/controllers/carreras_controller.dart'; +import 'package:mi_utem/models/carrera.dart'; +import 'package:mi_utem/models/usuario.dart'; +import 'package:mi_utem/services/analytics_service.dart'; +import 'package:mi_utem/services/auth_service.dart'; +import 'package:mi_utem/services/perfil_service.dart'; + +class UserController extends GetxController { + static const _storageTokenKey = 'token'; + static const _storageUserKey = 'user'; + static const _storageFirstTimeKey = 'esAntiguo'; + static const _storagePasswordKey = 'contrasenia'; + + static final GetStorage _box = GetStorage(); + static final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); + + static UserController get to => Get.find(); + + final user = Rxn(null); + final selectedCarrera = Rxn(); + + List get roles { + final roles = []; + + if (user.value != null) { + final carreras = CarrerasController.to.state; + if (carreras != null && carreras.length > 0) { + roles.add(Role.hasCareers); + final hasActive = carreras.firstWhereOrNull((c) => c.isActive) != null; + + if (hasActive) { + roles.add(Role.hasActiveCareer); + } + } + } + return roles; + } + + @override + void onInit() { + super.onInit(); + + final user = _getStoredUser(); + if (user != null) { + this.user.value = user; + } + + ever(this.user, (Usuario? user) { + if (UserController.to.isLoggedIn && user != null) { + AnalyticsService.setUser(user); + } else { + AnalyticsService.removeUser(); + } + }); + } + + Future login(String correo, String contrasenia) async { + try { + final user = await AuthService.login(correo, contrasenia); + + _box.write(_storageFirstTimeKey, true); + + _storeCredentials(user, contrasenia); + _storeUser(user); + + return user; + } catch (e) { + throw e; + } + } + + bool get isFirstTime { + return _box.read(_storageFirstTimeKey).toString() == 'true'; + } + + bool get isLoggedIn { + return user.value != null; + } + + Future logOut() async { + try { + invalidateToken(); + _box.remove(_storageUserKey); + await _secureStorage.deleteAll(); + try { + await PerfilService.deleteFcmToken(); + } catch (e) {} + user.value = null; + } catch (e) { + print(e.toString()); + throw e; + } + } + + Future refreshToken() async { + try { + final email = user.value?.correoUtem; + final password = await _secureStorage.read(key: _storagePasswordKey); + + if (email != null && password != null) { + final token = await AuthService.refreshToken(email, password); + _storeToken(token); + + return token; + } + throw Exception("No se pudo refrescar el token"); + } catch (e) { + print(e.toString()); + rethrow; + } + } + + void _storeUser(Usuario userToStore) async { + user.value = userToStore; + + _box.write(_storageUserKey, jsonEncode(userToStore.toJson())); + } + + static void _storeCredentials(Usuario user, String contrasenia) async { + _box.write(_storageTokenKey, user.token); + await _secureStorage.write(key: _storagePasswordKey, value: contrasenia); + } + + Usuario? _getStoredUser() { + final user = _box.read(_storageUserKey); + if (user == null) return null; + + return Usuario.fromJson(jsonDecode(user)); + } + + static void _storeToken(String token) async { + _box.write(_storageTokenKey, token); + } + + String getToken() { + final token = _box.read(_storageTokenKey); + if (token == null) throw Exception("No se ha encontrado el token"); + + return token; + } + + void invalidateToken() async { + _box.remove(_storageTokenKey); + } + + void selectCarrera(Carrera carrera) { + selectedCarrera.value = carrera; + } +} diff --git a/lib/main.dart b/lib/main.dart index e48103c..cceb079 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,15 +9,14 @@ import 'package:flutter_uxcam/flutter_uxcam.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:mi_utem/config/constants.dart'; -import 'package:mi_utem/config/router.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/router.dart'; +import 'package:mi_utem/config/routes/routes.dart'; +import 'package:mi_utem/controllers/asignatura_controller.dart'; import 'package:mi_utem/controllers/calculator_controller.dart'; import 'package:mi_utem/controllers/carreras_controller.dart'; -import 'package:mi_utem/services/analytics_service.dart'; -import 'package:mi_utem/services/auth_service.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/services/background_service.dart'; import 'package:mi_utem/services/notification_service.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/themes/theme.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -44,27 +43,9 @@ void main() async { ); } -class MiUtem extends StatefulWidget { - @override - State createState() => _MiUtemState(); -} - -class _MiUtemState extends State { +class MiUtem extends StatelessWidget { final FirebaseAnalytics analytics = FirebaseAnalytics.instance; - final calculatorController = Get.put(CalculatorController()); - - @override - void initState() { - if (AuthService.isLoggedIn()) { - final user = PerfilService.getLocalUsuario(); - AnalyticsService.setUser(user); - } else { - AnalyticsService.removeUser(); - } - super.initState(); - } - @override Widget build(BuildContext context) { FlutterUxcam.optIntoSchematicRecordings(); @@ -81,7 +62,9 @@ class _MiUtemState extends State { debugShowCheckedModeBanner: false, title: 'Mi UTEM', initialBinding: BindingsBuilder(() { + Get.put(UserController(), permanent: true); Get.put(CarrerasController(), permanent: true); + Get.put(CalculatorController(), permanent: true); }), theme: MainTheme.theme, navigatorObservers: [ diff --git a/lib/models/carrera.dart b/lib/models/carrera.dart index e42b32b..b7f5cb7 100644 --- a/lib/models/carrera.dart +++ b/lib/models/carrera.dart @@ -1,6 +1,8 @@ import 'package:recase/recase.dart'; class Carrera { + static const _stateActive = 'Regular'; + String? id; String? nombre; String? estado; @@ -9,6 +11,8 @@ class Carrera { Carrera({this.id, this.nombre, this.estado, this.codigo, this.plan}); + bool get isActive => estado == _stateActive; + factory Carrera.fromJson(Map? json) { if (json == null) { return Carrera(); diff --git a/lib/models/usuario.dart b/lib/models/usuario.dart index 66d0dd3..21d8e8e 100644 --- a/lib/models/usuario.dart +++ b/lib/models/usuario.dart @@ -1,6 +1,38 @@ import 'package:mi_utem/models/rut.dart'; import 'package:recase/recase.dart'; +enum Role { + hasCareers, + hasActiveCareer, + hasLunchBenefit, +} + +extension RoleExtension on Role { + String get name { + switch (this) { + case Role.hasCareers: + return 'hasCareers'; + case Role.hasActiveCareer: + return 'hasActiveCareer'; + case Role.hasLunchBenefit: + return 'hasLunchBenefit'; + } + } + + static Role? fromName(String name) { + switch (name) { + case 'hasCareers': + return Role.hasCareers; + case 'hasActiveCareer': + return Role.hasActiveCareer; + case 'hasLunchBenefit': + return Role.hasLunchBenefit; + default: + return null; + } + } +} + class Usuario { String? nombre; String? nombres; @@ -11,15 +43,16 @@ class Usuario { String? fotoUrl; Rut? rut; - Usuario( - {this.correoUtem, - this.correoPersonal, - this.token, - this.nombres, - this.nombre, - this.fotoUrl, - this.apellidos, - this.rut}); + Usuario({ + this.correoUtem, + this.correoPersonal, + this.token, + this.nombres, + this.nombre, + this.fotoUrl, + this.apellidos, + this.rut, + }); factory Usuario.fromJson(Map? json) { if (json == null) { @@ -99,7 +132,6 @@ class Usuario { 'nombreCompleto': nombreCompleto, 'correoUtem': correoUtem, 'correoPersonal': correoPersonal, - 'token': token, 'fotoUrl': fotoUrl, 'nombres': nombres, 'apellidos': apellidos, diff --git a/lib/screens/asignatura_detalle_screen.dart b/lib/screens/asignatura/asignatura_detalle_screen.dart similarity index 92% rename from lib/screens/asignatura_detalle_screen.dart rename to lib/screens/asignatura/asignatura_detalle_screen.dart index 5553079..357047d 100644 --- a/lib/screens/asignatura_detalle_screen.dart +++ b/lib/screens/asignatura/asignatura_detalle_screen.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mdi/mdi.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/controllers/asignatura_controller.dart'; import 'package:mi_utem/controllers/calculator_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; -import 'package:mi_utem/screens/asignatura_estudiantes_tab.dart'; -import 'package:mi_utem/screens/asignatura_notas_tab.dart'; -import 'package:mi_utem/screens/asignatura_resumen_tab.dart'; +import 'package:mi_utem/screens/asignatura/asignatura_estudiantes_tab.dart'; +import 'package:mi_utem/screens/asignatura/asignatura_notas_tab.dart'; +import 'package:mi_utem/screens/asignatura/asignatura_resumen_tab.dart'; import 'package:mi_utem/services/remote_config/remote_config.dart'; import 'package:mi_utem/services/review_service.dart'; import 'package:mi_utem/widgets/custom_app_bar.dart'; diff --git a/lib/screens/asignatura_estudiantes_tab.dart b/lib/screens/asignatura/asignatura_estudiantes_tab.dart similarity index 100% rename from lib/screens/asignatura_estudiantes_tab.dart rename to lib/screens/asignatura/asignatura_estudiantes_tab.dart diff --git a/lib/screens/asignatura_notas_tab.dart b/lib/screens/asignatura/asignatura_notas_tab.dart similarity index 100% rename from lib/screens/asignatura_notas_tab.dart rename to lib/screens/asignatura/asignatura_notas_tab.dart diff --git a/lib/screens/asignatura_resumen_tab.dart b/lib/screens/asignatura/asignatura_resumen_tab.dart similarity index 100% rename from lib/screens/asignatura_resumen_tab.dart rename to lib/screens/asignatura/asignatura_resumen_tab.dart diff --git a/lib/screens/asignaturas_lista_screen.dart b/lib/screens/asignatura/asignaturas_lista_screen.dart similarity index 98% rename from lib/screens/asignaturas_lista_screen.dart rename to lib/screens/asignatura/asignaturas_lista_screen.dart index 4ada864..4ad0d1f 100644 --- a/lib/screens/asignaturas_lista_screen.dart +++ b/lib/screens/asignatura/asignaturas_lista_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mdi/mdi.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/controllers/asignaturas_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/services/remote_config/remote_config.dart'; diff --git a/lib/screens/credencial_screen.dart b/lib/screens/credencial_screen.dart index 998afc1..9ad5e81 100644 --- a/lib/screens/credencial_screen.dart +++ b/lib/screens/credencial_screen.dart @@ -1,17 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_windowmanager/flutter_windowmanager.dart'; +import 'package:get/get.dart'; import 'package:mdi/mdi.dart'; -import 'package:mi_utem/controllers/carreras_controller.dart'; -import 'package:mi_utem/models/carrera.dart'; -import 'package:mi_utem/models/usuario.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/services/analytics_service.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/services/review_service.dart'; import 'package:mi_utem/widgets/credencial_card.dart'; import 'package:mi_utem/widgets/custom_app_bar.dart'; import 'package:mi_utem/widgets/custom_error_widget.dart'; import 'package:mi_utem/widgets/flip_widget.dart'; -import 'package:mi_utem/widgets/loading_indicator.dart'; class CredencialScreen extends StatefulWidget { CredencialScreen({ @@ -23,15 +20,12 @@ class CredencialScreen extends StatefulWidget { } class _CredencialScreenState extends State { - Future? _future; - Usuario? _usuario; FlipController _flipController = FlipController(); @override void initState() { ReviewService.addScreen("CredencialScreen"); _secureScreen(); - _future = _getData(); super.initState(); } @@ -49,109 +43,46 @@ class _CredencialScreenState extends State { super.dispose(); } - Future _getData() async { - try { - Usuario usuario = PerfilService.getLocalUsuario(); - setState(() { - _usuario = usuario; - }); - return usuario; - } catch (e) { - throw e; - } - } + UserController get controller => UserController.to; @override Widget build(BuildContext context) { - Carrera? carreraActiva = CarrerasController.to.selectedCarrera.value; - return Scaffold( - appBar: CustomAppBar( - title: Text("Credencial universitaria"), - actions: [ - IconButton( - icon: Icon(_flipController.actualFace == FlipController.front - ? Icons.info - : Mdi.accountCircle), - onPressed: () { - _flipController.flip!(); - }, - ), - ], - ), - backgroundColor: Colors.grey[200], - body: FutureBuilder( - future: _future, - builder: (context, snapshot) { - if (snapshot.hasError) { + appBar: CustomAppBar( + title: Text("Credencial universitaria"), + actions: [ + IconButton( + icon: Icon(_flipController.actualFace == FlipController.front + ? Icons.info + : Mdi.accountCircle), + onPressed: () { + _flipController.flip!(); + }, + ), + ], + ), + backgroundColor: Colors.grey[200], + body: Obx(() { + final user = controller.user.value; + final selectedCarrera = controller.selectedCarrera.value; + + if (user == null || selectedCarrera == null) { return CustomErrorWidget( - title: "Ocurrió un error al generar tu crendencial", - error: snapshot.error); - } else { - if (snapshot.hasData) { - if (_usuario!.rut != null && - carreraActiva!.nombre != null && - carreraActiva.nombre!.isNotEmpty) { - return Center( - child: SafeArea( - child: CredencialCard( - usuario: _usuario, - carrera: carreraActiva, - controller: _flipController, - onFlip: (direction) => _onFlip(), - ), - ), - ); - } else { - return Container( - padding: EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "😕", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 50, - ), - ), - Container(height: 15), - Text( - "Ocurrió un error al generar tu credencial", - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - Container(height: 15), - Text( - snapshot.error.toString(), - textAlign: TextAlign.center, - ), - ], - ), - ); - } - } else { - return Container( - padding: EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Center( - child: LoadingIndicator(), - ), - ), - ], - ), - ); - } + title: "Ocurrió un error al generar tu crendencial", + ); } - }, - ), - ); + + return Center( + child: SafeArea( + child: CredencialCard( + usuario: user, + carrera: selectedCarrera, + controller: _flipController, + onFlip: (direction) => _onFlip(), + ), + ), + ); + })); } void _onFlip() { diff --git a/lib/screens/docentes_screen.dart b/lib/screens/docentes_screen.dart index 5875a03..6971f07 100644 --- a/lib/screens/docentes_screen.dart +++ b/lib/screens/docentes_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/screens/usuario_screen.dart'; import 'package:mi_utem/services/docentes_service.dart'; diff --git a/lib/screens/login_screen/login_screen.dart b/lib/screens/login_screen/login_screen.dart index ca11827..77fcf52 100644 --- a/lib/screens/login_screen/login_screen.dart +++ b/lib/screens/login_screen/login_screen.dart @@ -8,11 +8,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/helpers/snackbars.dart'; import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/analytics_service.dart'; -import 'package:mi_utem/services/auth_service.dart'; import 'package:mi_utem/services/remote_config/remote_config.dart'; import 'package:mi_utem/widgets/acerca_dialog.dart'; import 'package:mi_utem/widgets/dialogs/monkey_error_dialog.dart'; @@ -254,8 +254,8 @@ class _LoginScreenState extends State { ); try { - bool esPrimeraVez = await AuthService.esPrimeraVez(); - Usuario usuario = await AuthService.login(correo, contrasenia, true); + bool isFirstTime = UserController.to.isFirstTime; + Usuario usuario = await UserController.to.login(correo, contrasenia); AnalyticsService.logEvent('login'); AnalyticsService.setUser(usuario); @@ -264,7 +264,7 @@ class _LoginScreenState extends State { Routes.home, ); - if (esPrimeraVez) { + if (isFirstTime) { Get.dialog( AcercaDialog(), ); diff --git a/lib/screens/main_screen.dart b/lib/screens/main/main_screen.dart similarity index 72% rename from lib/screens/main_screen.dart rename to lib/screens/main/main_screen.dart index bd0759d..93d3634 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main/main_screen.dart @@ -1,13 +1,8 @@ -import "dart:convert"; -import "dart:math"; - import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; import "package:flutter/services.dart"; -import "package:flutter_markdown/flutter_markdown.dart"; -import "package:get/get.dart"; import 'package:mi_utem/controllers/grades_changes_controller.dart'; -import "package:mi_utem/models/usuario.dart"; +import "package:mi_utem/screens/main/widgets/greetings.dart"; import "package:mi_utem/services/perfil_service.dart"; import "package:mi_utem/services/remote_config/remote_config.dart"; import "package:mi_utem/services/review_service.dart"; @@ -19,8 +14,7 @@ import "package:mi_utem/widgets/permisos_section.dart"; import "package:mi_utem/widgets/quick_menu_section.dart"; class MainScreen extends StatefulWidget { - final Usuario usuario; - MainScreen({Key? key, required this.usuario}) : super(key: key); + MainScreen({Key? key}) : super(key: key); @override _MainScreenState createState() => _MainScreenState(); @@ -50,22 +44,13 @@ class _MainScreenState extends State { super.dispose(); } - String get _greetingText { - List texts = jsonDecode(RemoteConfigService.greetings); - - Random random = new Random(); - String text = texts[random.nextInt(texts.length)]; - text = text.replaceAll("%name", widget.usuario.primerNombre); - return text; - } - @override Widget build(BuildContext context) { final banners = RemoteConfigService.banners; return Scaffold( appBar: CustomAppBar(title: Text("Inicio")), - drawer: CustomDrawer(usuario: widget.usuario), + drawer: CustomDrawer(), floatingActionButton: kDebugMode ? FloatingActionButton( onPressed: () { @@ -83,17 +68,7 @@ class _MainScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container(height: 20), - Container( - padding: EdgeInsets.symmetric(horizontal: 20), - width: double.infinity, - child: MarkdownBody( - data: _greetingText, - styleSheet: MarkdownStyleSheet( - p: Get.textTheme.displayMedium! - .copyWith(fontWeight: FontWeight.normal), - ), - ), - ), + MainScreenGreetings(), Container(height: 20), PermisosCovidSection(), Container(height: 20), diff --git a/lib/screens/main/widgets/greetings.dart b/lib/screens/main/widgets/greetings.dart new file mode 100644 index 0000000..f73b28b --- /dev/null +++ b/lib/screens/main/widgets/greetings.dart @@ -0,0 +1,41 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:get/get.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; +import 'package:mi_utem/services/remote_config/remote_config.dart'; + +class MainScreenGreetings extends StatelessWidget { + const MainScreenGreetings({ + Key? key, + }) : super(key: key); + + String getGreetingText(String firstName) { + List texts = RemoteConfigService.greetings; + + Random random = new Random(); + String text = texts[random.nextInt(texts.length)]; + text = text.replaceAll("%name", firstName); + return text; + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 20), + width: double.infinity, + child: Obx( + () => MarkdownBody( + data: getGreetingText( + UserController.to.user.value?.primerNombre ?? "Desconocido", + ), + styleSheet: MarkdownStyleSheet( + p: Get.textTheme.displayMedium! + .copyWith(fontWeight: FontWeight.normal), + ), + ), + ), + ); + } +} diff --git a/lib/screens/permiso_covid_screen.dart b/lib/screens/permiso_covid_screen.dart index ade51a8..d376365 100644 --- a/lib/screens/permiso_covid_screen.dart +++ b/lib/screens/permiso_covid_screen.dart @@ -7,7 +7,7 @@ import 'package:flutter_uxcam/flutter_uxcam.dart'; import 'package:get/get.dart'; import 'package:image/image.dart' as dartImage; import 'package:intl/intl.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/controllers/qr_pass_controller.dart'; import 'package:mi_utem/models/permiso_covid.dart'; import 'package:mi_utem/models/usuario.dart'; diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 7cb983b..8d14d1f 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -2,7 +2,7 @@ import 'package:flare_flutter/flare_actor.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/services/notification_service.dart'; import 'package:package_info_plus/package_info_plus.dart'; diff --git a/lib/screens/usuario_screen.dart b/lib/screens/usuario_screen.dart index 33e7fdc..133fcbf 100644 --- a/lib/screens/usuario_screen.dart +++ b/lib/screens/usuario_screen.dart @@ -3,7 +3,8 @@ import 'dart:core'; import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/docentes_service.dart'; @@ -58,7 +59,7 @@ class _UsuarioScreenState extends State { _usuario = usuario; }); } else { - usuario = PerfilService.getLocalUsuario(); + usuario = UserController.to.user.value!; setState(() { _usuario = usuario; }); @@ -77,7 +78,7 @@ class _UsuarioScreenState extends State { ); try { - Usuario usuario = await PerfilService.changeFoto(imagen); + Usuario? usuario = await PerfilService.changeFoto(imagen); Get.back(); setState(() { diff --git a/lib/services/asignaturas_service.dart b/lib/services/asignaturas_service.dart index 9bb7352..611ed9a 100644 --- a/lib/services/asignaturas_service.dart +++ b/lib/services/asignaturas_service.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/asignatura.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class AsignaturasService { @@ -14,7 +14,7 @@ class AsignaturasService { bool forceRefresh = false, }) async { final uri = "/v1/carreras/$carreraId/asignaturas"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; try { Response response = await _dio.get( @@ -22,7 +22,7 @@ class AsignaturasService { options: buildCacheOptions( Duration(days: 7), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); @@ -40,7 +40,7 @@ class AsignaturasService { bool forceRefresh = false, }) async { final uri = "/v1/asignaturas/$codigo"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; try { Response response = await _dio.get( @@ -48,7 +48,7 @@ class AsignaturasService { options: buildCacheOptions( Duration(days: 7), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 29d756f..d9f393f 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,150 +1,37 @@ import 'package:dio/dio.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_storage/get_storage.dart'; import 'package:mi_utem/models/usuario.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class AuthService { static final Dio _dio = DioMiUtemClient.initDio; static final GetStorage box = GetStorage(); - static Future login(String? correo, String? contrasenia, - [bool guardar = false]) async { - String uri = "/v1/auth"; + static Future login(String correo, String contrasenia) async { + final uri = "/v1/auth"; try { - final FlutterSecureStorage storage = new FlutterSecureStorage(); + final data = {'correo': correo, 'contrasenia': contrasenia}; + final response = await _dio.post(uri, data: data); - dynamic data = {'correo': correo, 'contrasenia': contrasenia}; - - Response response = await _dio.post(uri, data: data); - - Usuario usuario = Usuario(); - if (response.statusCode == 200) { - usuario = Usuario.fromJson(response.data); - box.write('token', usuario.token!); - if (usuario.nombres != null) { - box.write('nombres', usuario.nombres!); - } - if (usuario.apellidos != null) { - box.write('apellidos', usuario.apellidos!); - } - if (usuario.nombre != null) { - box.write('nombre', usuario.nombre!); - } - if (usuario.fotoUrl != null) { - box.write('fotoUrl', usuario.fotoUrl!); - } - if (usuario.correoUtem != null) { - box.write('correoUtem', usuario.correoUtem!); - } - if (usuario.correoPersonal != null) { - box.write('correoPersonal', usuario.correoPersonal!); - } - if (usuario.rut?.numero != null) { - box.write('rut', usuario.rut!.numero!); - } - box.write('esAntiguo', true); - - if (guardar) { - await storage.write(key: "contrasenia", value: contrasenia); - } - } - return usuario; - } on DioError catch (e) { - print(e.message); - throw e; + return Usuario.fromJson(response.data); } catch (e) { throw e; } } - static Future esPrimeraVez() async { - try { - bool? esAntiguo = box.read("esAntiguo"); - if (esAntiguo == null) { - return true; - } else { - return !esAntiguo; - } - } catch (e) { - print(e); - return false; - } - } + static Future refreshToken(String correo, String contrasenia) async { + final uri = "/v1/auth/refresh"; - static bool isLoggedIn() { try { - String? token = box.read("token"); - bool isLoggedIn = token != null && token.isNotEmpty; + final data = {'correo': correo, 'contrasenia': contrasenia}; - return isLoggedIn; - } catch (e) { - print(e); - return false; - } - } - - static Future logOut() async { - try { - final FlutterSecureStorage storage = new FlutterSecureStorage(); + final response = await DioMiUtemClient.authDio.post(uri, data: data); - box.remove("token"); - box.remove("correo"); - box.remove("nombres"); - box.remove("nombre"); - box.remove("apellidos"); - box.remove("fotoUrl"); - box.remove("rut"); - await storage.deleteAll(); - try { - await PerfilService.deleteFcmToken(); - } catch (e) {} - } catch (e) { - print(e.toString()); - throw e; - } - } - - static Future refreshToken() async { - String uri = "/v1/auth/refresh"; - - try { - final FlutterSecureStorage secureStorage = new FlutterSecureStorage(); - - String? correo = box.read("correoUtem"); - String? contrasenia = await secureStorage.read(key: "contrasenia"); - - if (correo != null && contrasenia != null) { - dynamic data = {'correo': correo, 'contrasenia': contrasenia}; - - Response response = await DioMiUtemClient.authDio.post(uri, data: data); - - String token = response.data['token']; - _storeToken(token); - - return token; - } - throw Exception("No se pudo refrescar el token"); + return response.data['token'] as String; } catch (e) { print(e.toString()); rethrow; } } - - static void _storeToken(String token) async { - box.write('token', token); - } - - static String getToken() { - final token = box.read('token'); - if (token == null) throw Exception("No se ha encontrado el token"); - - return token; - } - - static void invalidateToken() async { - box.remove('token'); - } } diff --git a/lib/services/carreras_service.dart b/lib/services/carreras_service.dart index 3326442..c21cba4 100644 --- a/lib/services/carreras_service.dart +++ b/lib/services/carreras_service.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/carrera.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class CarreraService { @@ -9,14 +9,14 @@ class CarreraService { static Future> getCarreras({bool forceRefresh = false}) async { const uri = "/v1/carreras"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; Response response = await _dio.get( uri, options: buildCacheOptions( Duration(days: 7), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); diff --git a/lib/services/grades_service.dart b/lib/services/grades_service.dart index 4d13597..67a65a8 100644 --- a/lib/services/grades_service.dart +++ b/lib/services/grades_service.dart @@ -2,8 +2,8 @@ import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; import 'package:get_storage/get_storage.dart'; import 'package:mi_utem/controllers/grades_changes_controller.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/grades.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class GradesService { @@ -17,7 +17,7 @@ class GradesService { bool saveGrades = true, }) async { final uri = "/v1/carreras/$carreraId/asignaturas/$asignaturaId/notas"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; Response response = await _dio.get( uri, @@ -25,7 +25,7 @@ class GradesService { Duration(days: 1), maxStale: Duration(days: 7), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); diff --git a/lib/services/horarios_service.dart b/lib/services/horarios_service.dart index b185f15..bdcf22a 100644 --- a/lib/services/horarios_service.dart +++ b/lib/services/horarios_service.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/horario.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class HorarioService { @@ -14,7 +14,7 @@ class HorarioService { bool forceRefresh = false, }) async { final uri = "/v1/carreras/$carreraId/horarios"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; try { Response response = await _dio.get( @@ -22,7 +22,7 @@ class HorarioService { options: buildCacheOptions( Duration(days: 30), forceRefresh: true, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); diff --git a/lib/services/perfil_service.dart b/lib/services/perfil_service.dart index 7adfa25..3d87772 100644 --- a/lib/services/perfil_service.dart +++ b/lib/services/perfil_service.dart @@ -1,7 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:dio/dio.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:mi_utem/models/rut.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/notification_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; @@ -10,38 +10,7 @@ class PerfilService { static final Dio _dio = DioMiUtemClient.authDio; static final GetStorage box = GetStorage(); - static Usuario getLocalUsuario() { - try { - String? token = box.read("token"); - String? nombres = box.read("nombres"); - String? apellidos = box.read("apellidos"); - String? nombre = box.read("nombre"); - String? fotoUrl = box.read("fotoUrl"); - Rut? rut = box.read("rut") != null - ? (box.read("rut") is int - ? Rut.deEntero(box.read("rut")) - : Rut.deString(box.read("rut"))) - : null; - String? correoUtem = box.read("correoUtem"); - String? correoPersonal = box.read("correoPersonal"); - - return Usuario( - token: token, - nombres: nombres, - fotoUrl: fotoUrl, - nombre: nombre, - apellidos: apellidos, - rut: rut, - correoUtem: correoUtem, - correoPersonal: correoPersonal, - ); - } catch (e) { - print(e); - throw e; - } - } - - static Future changeFoto(String imagen) async { + static Future changeFoto(String imagen) async { String uri = "/v1/usuarios/foto"; try { @@ -53,9 +22,7 @@ class PerfilService { box.write('fotoUrl', fotoUrl); - Usuario usuario = getLocalUsuario(); - - return usuario; + return UserController.to.user.value; } on DioError catch (e) { print(e.message); throw e; @@ -88,13 +55,13 @@ class PerfilService { try { String? fcmToken = await NotificationService.fcm.requestFirebaseAppToken(); - Usuario usuario = PerfilService.getLocalUsuario(); + Usuario? usuario = UserController.to.user.value; CollectionReference usuariosCollection = FirebaseFirestore.instance.collection('usuarios'); await PerfilService.deleteFcmToken(); - usuariosCollection.doc(usuario.rut!.numero.toString()).set( + usuariosCollection.doc(usuario!.rut!.numero.toString()).set( { "fcmTokens": FieldValue.arrayUnion([fcmToken]), }, diff --git a/lib/services/permisos_covid_service.dart b/lib/services/permisos_covid_service.dart index 7d6e89f..f85b330 100644 --- a/lib/services/permisos_covid_service.dart +++ b/lib/services/permisos_covid_service.dart @@ -2,8 +2,8 @@ import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; import 'package:get_storage/get_storage.dart'; import 'package:mi_utem/config/logger.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/permiso_covid.dart'; -import 'package:mi_utem/services/perfil_service.dart'; import 'package:mi_utem/utils/dio_miutem_client.dart'; class PermisosCovidService { @@ -14,9 +14,9 @@ class PermisosCovidService { {bool forceRefresh = false}) async { const uri = "/v1/permisos"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; - logger.d("Obteniendo permisos de ${user.rut?.numero}"); + logger.d("Obteniendo permisos de ${user?.rut?.numero}"); try { Response response = await _dio.post( @@ -25,7 +25,7 @@ class PermisosCovidService { Duration(days: 300), maxStale: Duration(days: 300), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); @@ -42,7 +42,7 @@ class PermisosCovidService { }) async { final uri = "/v1/permisos/$id"; - final user = PerfilService.getLocalUsuario(); + final user = UserController.to.user.value; try { Response response = await _dio.post( @@ -51,7 +51,7 @@ class PermisosCovidService { Duration(days: 180), maxStale: Duration(days: 365), forceRefresh: forceRefresh, - subKey: user.rut?.numero.toString(), + subKey: user?.rut?.numero.toString(), ), ); diff --git a/lib/services/remote_config/defaults.dart b/lib/services/remote_config/defaults.dart index eaba945..6acee44 100644 --- a/lib/services/remote_config/defaults.dart +++ b/lib/services/remote_config/defaults.dart @@ -185,53 +185,45 @@ final _defaults = { RemoteConfigServiceKeys.egHabilitados: true, RemoteConfigServiceKeys.drawerMenu: jsonEncode([ { - "nombre": "Perfil", - "icono": { + "title": "Perfil", + "icon": { "codePoint": Icons.person.codePoint, "fontFamily": 'MaterialIcons' }, - "mostrar": true, - "esNuevo": false + "route": Routes.perfil, + "show": true, }, { - "nombre": "Asignaturas", - "icono": { + "title": "Asignaturas", + "icon": { "codePoint": Icons.book.codePoint, "fontFamily": 'MaterialIcons' }, - "mostrar": true, - "esNuevo": false + "route": Routes.asignaturas, + "show": true, }, { - "nombre": "Horario", - "icono": { + "title": "Horario", + "icon": { "codePoint": Mdi.clockTimeEight.codePoint, "fontFamily": 'Material Design Icons', "fontPackage": "mdi" }, - "mostrar": true, - "esNuevo": false + "route": Routes.horario, + "show": true }, { - "nombre": "Credencial", - "icono": { + "title": "Credencial", + "icon": { "codePoint": Mdi.cardAccountDetails.codePoint, "fontFamily": 'Material Design Icons', "fontPackage": "mdi" }, - "mostrar": true, - "esNuevo": true + "show": true, + "route": Routes.credencial, + "requiredRoles": [Role.hasActiveCareer.name], + "badge": "Nuevo" }, - // { - // "nombre": "Docentes", - // "icono": { - // "codePoint": Mdi.accountTie.codePoint, - // "fontFamily": 'Material Design Icons', - // "fontPackage": "mdi" - // }, - // "mostrar": true, - // "esNuevo": true - // }, ]), RemoteConfigServiceKeys.greetings: jsonEncode(['Que gusto verte, **%name**']), RemoteConfigServiceKeys.quickMenu: jsonEncode([ diff --git a/lib/services/remote_config/remote_config.dart b/lib/services/remote_config/remote_config.dart index c288086..d4a7c72 100644 --- a/lib/services/remote_config/remote_config.dart +++ b/lib/services/remote_config/remote_config.dart @@ -4,8 +4,11 @@ import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:mdi/mdi.dart'; +import 'package:mi_utem/config/routes/routes.dart'; +import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/remote_config/keys.dart'; import 'package:mi_utem/widgets/banner.dart'; +import 'package:mi_utem/widgets/custom_drawer.dart'; part 'defaults.dart'; @@ -55,8 +58,13 @@ class RemoteConfigService { _getString(RemoteConfigServiceKeys.homeProntoTexto); static final prontoEg = _getString(RemoteConfigServiceKeys.prontoEg); static final egHabilitados = _getBool(RemoteConfigServiceKeys.egHabilitados); - static final drawerMenu = _getString(RemoteConfigServiceKeys.drawerMenu); - static final greetings = _getString(RemoteConfigServiceKeys.greetings); + static final drawerMenu = IDrawerItem.fromJsonList( + jsonDecode(_getString(RemoteConfigServiceKeys.drawerMenu))); + static final greetings = + (jsonDecode(_getString(RemoteConfigServiceKeys.greetings)) + as List) + .map((e) => e.toString()) + .toList(); static final quickMenu = _getString(RemoteConfigServiceKeys.quickMenu); factory RemoteConfigService() => instance; diff --git a/lib/utils/dio_miutem_client.dart b/lib/utils/dio_miutem_client.dart index 0db10d8..0730480 100644 --- a/lib/utils/dio_miutem_client.dart +++ b/lib/utils/dio_miutem_client.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:dio/dio.dart'; import 'package:dio_http_cache/dio_http_cache.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:mi_utem/services/auth_service.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; class DioMiUtemClient { static const bool isProduction = bool.fromEnvironment('dart.vm.product'); @@ -45,7 +45,7 @@ class AuthInterceptor extends QueuedInterceptor { final RequestInterceptorHandler handler, ) async { try { - final token = AuthService.getToken(); + final token = UserController.to.getToken(); options._setAuthenticationHeader(token); @@ -75,7 +75,7 @@ class AuthInterceptor extends QueuedInterceptor { // Force refresh auth token try { - final token = await AuthService.refreshToken(); + final token = await UserController.to.refreshToken(); log("Refreshing token, attempt $attempt..."); @@ -93,7 +93,7 @@ class AuthInterceptor extends QueuedInterceptor { } Future _onErrorRefreshingToken() async { - AuthService.invalidateToken(); + UserController.to.invalidateToken(); } } diff --git a/lib/widgets/acerca_dialog.dart b/lib/widgets/acerca_dialog.dart index 9aba895..fdde936 100644 --- a/lib/widgets/acerca_dialog.dart +++ b/lib/widgets/acerca_dialog.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/widgets/acerca_aplicacion_content.dart'; class AcercaDialog extends StatefulWidget { diff --git a/lib/widgets/acerca_screen.dart b/lib/widgets/acerca_screen.dart index 30a7613..3a6b940 100644 --- a/lib/widgets/acerca_screen.dart +++ b/lib/widgets/acerca_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/models/usuario.dart'; import 'package:mi_utem/services/analytics_service.dart'; import 'package:mi_utem/services/remote_config/remote_config.dart'; diff --git a/lib/widgets/bloque_ramo_card.dart b/lib/widgets/bloque_ramo_card.dart index c69b0b6..c486533 100644 --- a/lib/widgets/bloque_ramo_card.dart +++ b/lib/widgets/bloque_ramo_card.dart @@ -1,5 +1,7 @@ import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/controllers/horario_controller.dart'; import 'package:mi_utem/models/horario.dart'; import 'package:mi_utem/services/analytics_service.dart'; diff --git a/lib/widgets/custom_drawer.dart b/lib/widgets/custom_drawer.dart index 1880c31..35e4859 100644 --- a/lib/widgets/custom_drawer.dart +++ b/lib/widgets/custom_drawer.dart @@ -1,44 +1,76 @@ -import 'dart:convert'; +import 'dart:developer'; import 'package:badges/badges.dart' as badge; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mdi/mdi.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; +import 'package:mi_utem/controllers/user_controller.dart'; import 'package:mi_utem/models/usuario.dart'; -import 'package:mi_utem/services/auth_service.dart'; import 'package:mi_utem/services/remote_config/remote_config.dart'; import 'package:mi_utem/services/review_service.dart'; import 'package:mi_utem/themes/theme.dart'; import 'package:mi_utem/widgets/profile_photo.dart'; -class CustomDrawer extends StatelessWidget { - final Usuario usuario; - CustomDrawer({Key? key, required this.usuario}) : super(key: key); - - String? _getRoute(String? name) { - switch (name) { - case "Perfil": - return Routes.perfil; - case "Asignaturas": - return Routes.asignaturas; - case "Horario": - return Routes.horario; - case "Credencial": - return Routes.credencial; - // case "Docentes": - // return DocentesScreen(); - // break; - default: - return Routes.home; +class IDrawerItem { + final String title; + final IconData icon; + final String route; + final List requiredRoles; + final bool show; + final String? badge; + + IDrawerItem({ + required this.title, + required this.icon, + required this.route, + this.requiredRoles = const [], + this.show = true, + this.badge, + }); + + factory IDrawerItem.fromJson(Map json) { + final roles = []; + if (json['requiredRoles'] != null) { + for (String roleString in json['requiredRoles']) { + final role = RoleExtension.fromName(roleString); + if (role != null) { + roles.add(role); + } + } } + + return IDrawerItem( + title: json['title'], + icon: IconData( + json['icon']['codePoint'], + fontFamily: json['icon']['fontFamily'], + fontPackage: json['icon']['fontPackage'], + ), + route: json['route'], + badge: json['badge'], + show: json['show'] ?? true, + requiredRoles: roles, + ); } - List? get _menu { - return jsonDecode(RemoteConfigService.drawerMenu) - .where((e) => e['mostrar'] == true) - .toList(); + static List fromJsonList(dynamic json) { + if (json == null) { + return []; + } + List list = []; + for (var item in json) { + list.add(IDrawerItem.fromJson(item)); + } + return list; } +} + +List _filteredMenu = + RemoteConfigService.drawerMenu.where((e) => e.show).toList(); + +class CustomDrawer extends StatelessWidget { + CustomDrawer({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -57,59 +89,39 @@ class CustomDrawer extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - UserAccountsDrawerHeader( - accountEmail: Text( - usuario.correoUtem ?? usuario.correoPersonal ?? ""), - accountName: Text( - usuario.nombreCompleto ?? "", - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - currentAccountPicture: ProfilePhoto( - usuario: usuario, - radius: 30, - ), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomLeft, - end: Alignment.topRight, - colors: [ - MainTheme.utemAzul, - MainTheme.utemVerde, - ], + Obx(() { + final user = UserController.to.user.value; + return UserAccountsDrawerHeader( + accountEmail: Text(user?.correoUtem ?? + user?.correoPersonal ?? + "Sin correo"), + accountName: Text( + user?.nombreCompleto ?? "Sin nombre", + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + currentAccountPicture: ProfilePhoto( + usuario: user, + radius: 30, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomLeft, + end: Alignment.topRight, + colors: [ + MainTheme.utemAzul, + MainTheme.utemVerde, + ], + ), + ), + ); + }), + for (var e in _filteredMenu) + Obx( + () => CustomDrawerItem( + item: e, + currentUserRoles: UserController.to.roles, ), - ), - ), - for (var e in _menu!) - ListTile( - leading: Icon(IconData(e["icono"]["codePoint"], - fontFamily: e["icono"]["fontFamily"], - fontPackage: e["icono"]["fontPackage"])), - title: Text(e["nombre"]), - trailing: e["esNuevo"] - ? badge.Badge( - shape: badge.BadgeShape.square, - borderRadius: BorderRadius.circular(10), - padding: EdgeInsets.symmetric( - horizontal: 6, vertical: 3), - elevation: 0, - badgeContent: Text( - 'Nuevo', - style: TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ) - : null, - onTap: () async { - String? route = _getRoute(e["nombre"]); - if (route != null) { - Get.toNamed(route); - ReviewService.checkAndRequestReview(); - } - }, ), Expanded( child: SafeArea( @@ -129,7 +141,7 @@ class CustomDrawer extends StatelessWidget { leading: Icon(Mdi.closeCircle), title: Text('Cerrar sesión'), onTap: () async { - await AuthService.logOut(); + await UserController.to.logOut(); await Get.offAllNamed(Routes.home); }, @@ -147,3 +159,51 @@ class CustomDrawer extends StatelessWidget { ); } } + +class CustomDrawerItem extends StatelessWidget { + const CustomDrawerItem({ + Key? key, + required this.item, + required this.currentUserRoles, + }) : super(key: key); + + final IDrawerItem item; + final List currentUserRoles; + + @override + Widget build(BuildContext context) { + final currentUserRolesSet = currentUserRoles.toSet(); + + log("CustomDrawerItem: ${item.title} requiredRoles ${item.requiredRoles} currentUserRoles $currentUserRoles"); + + if (item.requiredRoles.isNotEmpty && + !currentUserRolesSet.containsAll(item.requiredRoles)) { + return Container(); + } + + return ListTile( + leading: Icon(item.icon), + title: Text(item.title), + trailing: item.badge != null + ? badge.Badge( + shape: badge.BadgeShape.square, + borderRadius: BorderRadius.circular(10), + padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3), + elevation: 0, + badgeContent: Text( + item.badge!, + style: TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ) + : null, + onTap: () async { + Get.toNamed(item.route); + ReviewService.checkAndRequestReview(); + }, + ); + } +} diff --git a/lib/widgets/default_network_image.dart b/lib/widgets/default_network_image.dart index b73470a..565b1e1 100644 --- a/lib/widgets/default_network_image.dart +++ b/lib/widgets/default_network_image.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/widgets/image_view_screen.dart'; class DefaultNetworkImage extends StatelessWidget { diff --git a/lib/widgets/dialogs/not_ready_dialog.dart b/lib/widgets/dialogs/not_ready_dialog.dart index f0818e5..17d0994 100644 --- a/lib/widgets/dialogs/not_ready_dialog.dart +++ b/lib/widgets/dialogs/not_ready_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/themes/theme.dart'; import 'package:mi_utem/widgets/error_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/widgets/permiso_card.dart b/lib/widgets/permiso_card.dart index 876207f..c76b4cd 100644 --- a/lib/widgets/permiso_card.dart +++ b/lib/widgets/permiso_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; import 'package:mi_utem/models/permiso_covid.dart'; import 'package:mi_utem/themes/theme.dart'; diff --git a/lib/widgets/quick_menu_card.dart b/lib/widgets/quick_menu_card.dart index a333747..4ad32c3 100644 --- a/lib/widgets/quick_menu_card.dart +++ b/lib/widgets/quick_menu_card.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:gradient_widgets/gradient_widgets.dart'; import 'package:hexcolor/hexcolor.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; class QuickMenuCard extends StatelessWidget { const QuickMenuCard({Key? key, required this.card}) : super(key: key); diff --git a/lib/widgets/sad_dialog.dart b/lib/widgets/sad_dialog.dart index cf22aee..8aa10dc 100644 --- a/lib/widgets/sad_dialog.dart +++ b/lib/widgets/sad_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:mi_utem/config/routes.dart'; +import 'package:mi_utem/config/routes/routes.dart'; final _formKey = GlobalKey();