bLOQUE p1 BACKEND Y SEGURIDAD, AUTENTICACION CON SUPABASE. jwt. RBAC CRUD
This commit is contained in:
@@ -1,69 +1,61 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import '../firebase_options.dart';
|
||||
|
||||
final bootstrapProvider = FutureProvider<void>((ref) async {
|
||||
await dotenv.load(fileName: 'assets/.env');
|
||||
|
||||
// Inicializar Firebase (si hay DefaultFirebaseOptions, úsalas; sino, intenta initializeApp() y espera que haya google-services/Info.plist)
|
||||
final FirebaseOptions? options = DefaultFirebaseOptions.currentPlatform;
|
||||
if (options != null) {
|
||||
await Firebase.initializeApp(options: options);
|
||||
} else {
|
||||
await Firebase.initializeApp();
|
||||
}
|
||||
|
||||
// Registrar handler para mensajes en background
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
});
|
||||
|
||||
// Handler top-level requerido por FCM
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
// Asegurar Firebase inicializado en background isolate
|
||||
try {
|
||||
await Firebase.initializeApp();
|
||||
} catch (_) {
|
||||
// ignore if already initialized
|
||||
}
|
||||
// Aquí puedes procesar y guardar la notificación si hace falta
|
||||
debugPrint(
|
||||
'FCM background message received: ${message.messageId} | data: ${message.data}',
|
||||
);
|
||||
}
|
||||
|
||||
final apiClientProvider = Provider<Dio>((ref) {
|
||||
final baseUrl = dotenv.env['API_BASE_URL'] ?? 'http://10.0.2.2:8000';
|
||||
|
||||
return Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
receiveTimeout: const Duration(seconds: 15),
|
||||
headers: const <String, dynamic>{'Content-Type': 'application/json'},
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
final secureStorageProvider = Provider<FlutterSecureStorage>((ref) {
|
||||
return const FlutterSecureStorage();
|
||||
});
|
||||
import '../core/network/api_client.dart';
|
||||
import '../core/services/auth_controller.dart';
|
||||
import '../core/storage/secure_storage.dart';
|
||||
import 'bootstrap.dart' as bootstrap;
|
||||
import '../features/auth/login_page.dart';
|
||||
import '../features/auth/register_page.dart';
|
||||
import '../features/addresses/new_address_page.dart';
|
||||
|
||||
final routerProvider = Provider<GoRouter>((ref) {
|
||||
final authSnapshot = ref.watch(authControllerProvider);
|
||||
final isAuthenticated = authSnapshot.asData?.value.isAuthenticated ?? false;
|
||||
|
||||
return GoRouter(
|
||||
initialLocation: '/home',
|
||||
redirect: (context, state) {
|
||||
final location = state.matchedLocation;
|
||||
final isAuthRoute = location == '/login' || location == '/register';
|
||||
|
||||
if (authSnapshot.isLoading) {
|
||||
return location == '/login' ? null : '/login';
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return isAuthRoute ? null : '/login';
|
||||
}
|
||||
|
||||
if (isAuthenticated && isAuthRoute) {
|
||||
return '/home';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
builder: (context, state) => const LoginPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
builder: (context, state) => const RegisterPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
builder: (context, state) => const HomePage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/addresses/new',
|
||||
name: 'addresses-new',
|
||||
builder: (context, state) => const NewAddressPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/status',
|
||||
name: 'status',
|
||||
@@ -78,9 +70,9 @@ class RecolectaApp extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bootstrap = ref.watch(bootstrapProvider);
|
||||
final bootstrapState = ref.watch(bootstrap.bootstrapProvider);
|
||||
|
||||
return bootstrap.when(
|
||||
return bootstrapState.when(
|
||||
loading: () => const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: BootstrapLoadingPage(),
|
||||
@@ -154,11 +146,24 @@ class HomePage extends ConsumerWidget {
|
||||
final dio = ref.read(apiClientProvider);
|
||||
final storage = ref.read(secureStorageProvider);
|
||||
final baseUrl = dio.options.baseUrl;
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Recolecta'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: authState.isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref.read(authControllerProvider.notifier).logout();
|
||||
if (context.mounted) {
|
||||
context.go('/login');
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.logout),
|
||||
tooltip: 'Salir',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => context.goNamed('status'),
|
||||
icon: const Icon(Icons.route),
|
||||
@@ -179,6 +184,11 @@ class HomePage extends ConsumerWidget {
|
||||
'La app ya carga .env, Riverpod y GoRouter para la base del MVP.',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextButton(
|
||||
onPressed: () => context.go('/addresses/new'),
|
||||
child: const Text('Agregar domicilio'),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_InfoCard(title: 'API base URL', value: baseUrl, icon: Icons.cloud),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Reference in New Issue
Block a user