Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/core/models/navigation/navigation_item.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import "package:flutter/material.dart";
import "package:miutem/core/models/user/perfil.dart";

class NavigationItem {

final Widget destination;
final String label;
final String featureFlag;
final List<Perfil> perfiles;
final IconData icon;

NavigationItem({required this.destination, required this.label, required this.featureFlag, required this.icon});
NavigationItem({required this.destination, required this.label, required this.featureFlag, required this.icon, this.perfiles = const []});
}
8 changes: 6 additions & 2 deletions lib/core/models/user/perfil.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
enum Perfil {
estudiante,
funcionario,
profesor,
profesor;

;
String get displayName => switch (this) {
Perfil.estudiante => "Estudiante",
Perfil.funcionario => "Funcionario",
Perfil.profesor => "Profesor",
};
}
8 changes: 8 additions & 0 deletions lib/core/services/auth_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class AuthService {

final SecureStorageRepository _secureStorageRepository = Get.find<SecureStorageRepository>();
bool idHasBeenSet = false;
Estudiante? _cachedEstudiante;

/// Returns the currently logged-in student from the in-memory cache.
/// Used for synchronous access (e.g. profile-based feature flags).
Estudiante? get cachedEstudiante => _cachedEstudiante;

Future<bool> isFirstTime() async => (await Preferencia.lastLogin.exists()) == false;

Expand All @@ -29,6 +34,7 @@ class AuthService {

Estudiante? estudiante = await _secureStorageRepository.getEstudiante();
if (estudiante != null && !forceRefresh) {
_cachedEstudiante = estudiante;
if(!idHasBeenSet) {
setUserIdentifier(estudiante);
idHasBeenSet = true;
Expand All @@ -49,6 +55,7 @@ class AuthService {
}

estudiante = Estudiante.fromJson(response.data["response"] as Map<String, dynamic>);
_cachedEstudiante = estudiante;
await _secureStorageRepository.setEstudiante(estudiante);
await Preferencia.lastLogin.set(DateTime.now().toIso8601String());
if(!idHasBeenSet) {
Expand Down Expand Up @@ -83,6 +90,7 @@ class AuthService {
}

Future<void> logout({ BuildContext? context}) async {
_cachedEstudiante = null;
await _secureStorageRepository.setEstudiante(null);
await _secureStorageRepository.setCredentials(null);
await Preferencia.onboardingStep.delete();
Expand Down
63 changes: 40 additions & 23 deletions lib/screens/asignaturas/lista_asignaturas_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import "package:flutter/material.dart";
import "package:get/get.dart";
import "package:miutem/core/models/asignaturas/asignatura.dart";
import "package:miutem/core/models/user/estudiante.dart";
import "package:miutem/core/models/user/perfil.dart";
import "package:miutem/core/services/asignaturas_service.dart";
import "package:miutem/core/services/auth_service.dart";
import "package:miutem/core/utils/utils.dart";
import "package:miutem/core/utils/logger.dart";
import "package:miutem/screens/asignaturas/widgets/acceso_rapido.dart";
import "package:miutem/screens/asignaturas/widgets/asignaturas_en_curso.dart";
import "package:miutem/screens/auth/login/login_screen.dart";

import "package:miutem/styles/styles.dart";
import "package:miutem/widgets/feature_flag.dart";

class AsignaturasScreen extends StatefulWidget {
const AsignaturasScreen({super.key});
Expand All @@ -25,22 +26,24 @@ class _AsignaturasScreenState extends State<AsignaturasScreen> {
@override
void initState() {
super.initState();
Get.find<AuthService>()
.login()
.then((estudiante) => setState(() => this.estudiante = estudiante),
onError: (err) {
Get.find<AuthService>().login().then((estudiante) {
if(mounted) setState(() => this.estudiante = estudiante);

if(!estudiante.perfiles.contains(Perfil.estudiante)) {
return;
}

Get.find<AsignaturasService>().getAsignaturas().then((asignaturas) {
if(mounted) setState(() => this.asignaturas = asignaturas);
}).catchError((error, stackTrace) {
logger.d("Error loading asignaturas: $error");
});
}, onError: (err) {
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (ctx) => const LoginScreen()));
Navigator.pushReplacement(context, MaterialPageRoute(builder: (ctx) => const LoginScreen()));
}
});
Get.find<AsignaturasService>()
.getAsignaturas()
.then((asignaturas) => setState(() => this.asignaturas = asignaturas),
onError: (err) {
logger.e("Error al cargar asignaturas", error: err);
});
}

@override
Expand All @@ -50,15 +53,26 @@ class _AsignaturasScreenState extends State<AsignaturasScreen> {
child: RefreshIndicator(
onRefresh: () async {
setState(() {
this.estudiante = null;
this.asignaturas = null;
});
final estudiante = await Get.find<AuthService>().login(forceRefresh: true);
final asignaturas = await Get.find<AsignaturasService>().getAsignaturas(forceRefresh: true);
setState(() {
this.estudiante = estudiante;
this.asignaturas = asignaturas;
estudiante = null;
asignaturas = null;
});
try {
final estudiante = await Get.find<AuthService>().login(forceRefresh: true);
final asignaturas = await Get.find<AsignaturasService>().getAsignaturas(forceRefresh: true);
if (!mounted) return;
setState(() {
this.estudiante = estudiante;
this.asignaturas = asignaturas;
});
} catch (error) {
logger.d("Error refreshing asignaturas: $error");
if (!mounted) return;
setState(() {
// Keep nulls or existing values; here we leave them null to indicate failure.
estudiante = null;
asignaturas = null;
});
}
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
Expand All @@ -71,7 +85,10 @@ class _AsignaturasScreenState extends State<AsignaturasScreen> {
Space.large,
const AccesoRapido(),
Space.large,
AsignaturasEnCurso(asignaturas: asignaturas),
FeatureFlag.profiles(
const [Perfil.estudiante],
child: AsignaturasEnCurso(asignaturas: asignaturas),
),
],
),
),
Expand Down
38 changes: 24 additions & 14 deletions lib/screens/asignaturas/widgets/acceso_rapido.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "package:flutter/material.dart";
import "package:miutem/styles/styles.dart";
import "package:miutem/core/models/user/perfil.dart";
import "package:miutem/screens/asignaturas/actions/acceso_rapido.dart";
import "package:miutem/styles/styles.dart";
import "package:miutem/widgets/feature_flag.dart";

class AccesoRapido extends StatelessWidget {
const AccesoRapido({super.key});
Expand All @@ -17,12 +19,16 @@ class AccesoRapido extends StatelessWidget {
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
children: [
CardAccesoRapido(
color: AppTheme.lightBlueCard,
colorDark: AppTheme.darkBlueCard,
label: "Horario",
icon: AppIcons.timetable,
onTap: () => visitarHorario(context),
FeatureFlag.profiles(
const [Perfil.estudiante],
showProfileRestrictionMessage: false,
child: CardAccesoRapido(
color: AppTheme.lightBlueCard,
colorDark: AppTheme.darkBlueCard,
label: "Horario",
icon: AppIcons.timetable,
onTap: () => visitarHorario(context),
),
),
HorizontalSpace.extraSmall,
CardAccesoRapido(
Expand All @@ -34,13 +40,17 @@ class AccesoRapido extends StatelessWidget {
onTap: () => visitarCalculadoraNotas(context),
),
HorizontalSpace.extraSmall,
CardAccesoRapido(
color: AppTheme.lightSalmonCard,
colorDark: AppTheme.darkSalmonCard,
label: "Malla Histórica",
icon: AppIcons.historicTimetable,
fill: 0,
onTap: () => visitarMallaHistorica(context),
FeatureFlag.profiles(
const [Perfil.estudiante],
showProfileRestrictionMessage: false,
child: CardAccesoRapido(
color: AppTheme.lightSalmonCard,
colorDark: AppTheme.darkSalmonCard,
label: "Malla Histórica",
icon: AppIcons.historicTimetable,
fill: 0,
onTap: () => visitarMallaHistorica(context),
),
),
],
),
Expand Down
100 changes: 55 additions & 45 deletions lib/screens/credencial/credencial_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:get/get.dart";
import "package:miutem/core/models/user/credencial/credencial_biblioteca.dart";
import "package:miutem/core/models/user/estudiante.dart";
import "package:miutem/core/models/user/perfil.dart";
import "package:miutem/core/services/auth_service.dart";
import "package:miutem/core/services/mi_utem/miutem_credencial_service.dart";
import "package:miutem/core/utils/utils.dart";
Expand All @@ -11,6 +12,7 @@ import "package:miutem/screens/credencial/widgets/credencial_biblioteca_front.da
import "package:miutem/screens/credencial/widgets/credencial_front.dart";
import "package:miutem/screens/credencial/widgets/flip_card.dart";
import "package:miutem/styles/styles.dart";
import "package:miutem/widgets/feature_flag.dart";

enum TipoCredencial { institucional, sibutem }

Expand Down Expand Up @@ -89,54 +91,62 @@ class _CredencialScreenState extends State<CredencialScreen> {
style: Theme.of(context).textTheme.headlineMedium,
),
Space.medium,
SizedBox(
width: double.infinity,
child: SegmentedButton<TipoCredencial>(
segments: const [
ButtonSegment(
value: TipoCredencial.institucional,
label: Text("Institucional"),
FeatureFlag.profiles(
const [Perfil.estudiante, Perfil.profesor],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: double.infinity,
child: SegmentedButton<TipoCredencial>(
segments: const [
ButtonSegment(
value: TipoCredencial.institucional,
label: Text("Institucional"),
),
ButtonSegment(
value: TipoCredencial.sibutem,
label: Text("SIBUTEM"),
),
],
selected: {_tipoCredencial},
onSelectionChanged: _onTipoCredencialChanged,
),
),
ButtonSegment(
value: TipoCredencial.sibutem,
label: Text("SIBUTEM"),
Space.small,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: _tipoCredencial == TipoCredencial.institucional ? FlipCard(
key: const ValueKey("institucional"),
front: CredencialFront(
usuario: estudiante,
availableHeight: cardHeight,
),
back: CredencialBack(
availableHeight: cardHeight,
),
) : FlipCard(
key: const ValueKey("sibutem"),
front: CredencialBibliotecaFront(
credencial: credencialBiblioteca,
availableHeight: cardHeight,
),
back: CredencialBibliotecaBack(
credencial: credencialBiblioteca,
estudiante: estudiante,
availableHeight: cardHeight,
),
),
),
Space.small,
Center(
child: Text("Toca la credencial para voltear",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
selected: {_tipoCredencial},
onSelectionChanged: _onTipoCredencialChanged,
),
),
Space.small,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: _tipoCredencial == TipoCredencial.institucional ? FlipCard(
key: const ValueKey("institucional"),
front: CredencialFront(
usuario: estudiante,
availableHeight: cardHeight,
),
back: CredencialBack(
availableHeight: cardHeight,
),
) : FlipCard(
key: const ValueKey("sibutem"),
front: CredencialBibliotecaFront(
credencial: credencialBiblioteca,
availableHeight: cardHeight,
),
back: CredencialBibliotecaBack(
credencial: credencialBiblioteca,
estudiante: estudiante,
availableHeight: cardHeight,
),
),
),
Space.small,
Center(
child: Text("Toca la credencial para voltear",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
Expand Down
17 changes: 13 additions & 4 deletions lib/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:get/get.dart";
import "package:miutem/core/models/horario.dart";
import "package:miutem/core/models/user/estudiante.dart";
import "package:miutem/core/models/user/perfil.dart";
import "package:miutem/core/services/auth_service.dart";
import "package:miutem/core/services/firebase/remote_config_service.dart";
import "package:miutem/core/utils/http/http_client.dart";
Expand All @@ -13,6 +14,7 @@ import "package:miutem/screens/home/widgets/clases_de_hoy/seccion_clases_de_hoy.
import "package:miutem/screens/home/widgets/novedades/card_novedades.dart";
import "package:miutem/screens/home/widgets/saludo.dart";
import "package:miutem/styles/styles.dart";
import "package:miutem/widgets/feature_flag.dart";

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
Expand Down Expand Up @@ -95,10 +97,13 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
Space.large,
SeccionClasesDeHoy(
errorAlCargarHorario: errorAlCargarHorario,
bloques: bloques,
cargarHorario: _cargarHorario,
FeatureFlag.profiles(
const [Perfil.estudiante],
child: SeccionClasesDeHoy(
errorAlCargarHorario: errorAlCargarHorario,
bloques: bloques,
cargarHorario: _cargarHorario,
),
),
],
),
Expand All @@ -108,6 +113,10 @@ class _HomeScreenState extends State<HomeScreen> {
);

Future<void> _cargarHorario({bool forceRefresh = false}) async {
if (estudiante == null || !estudiante!.perfiles.contains(Perfil.estudiante)) {
return;
}

setState(() {
errorAlCargarHorario = null;
bloques = null;
Expand Down
Loading
Loading