Files
packet/lib/presentation/app.dart
tzzrgit dac0c51483 PACKET v1.0.0 - Initial release
App móvil Flutter para capturar contenido multimedia, etiquetarlo con hashes y enviarlo a backends configurables.

Features:
- Captura de fotos, audio, video y archivos
- Sistema de etiquetas con bibliotecas externas (HST)
- Packs de etiquetas predefinidos
- Cola de reintentos (hasta 20 contenedores)
- Soporte GPS
- Hash SHA-256 auto-generado por contenedor
- Persistencia SQLite local
- Múltiples destinos configurables

Stack: Flutter 3.38.5, flutter_bloc, sqflite, dio

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 18:10:27 +01:00

148 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../core/theme/app_theme.dart';
import 'bloc/app/app_cubit.dart';
import 'bloc/app/app_state.dart';
import 'bloc/captura/captura_cubit.dart';
import 'bloc/etiquetas/etiquetas_cubit.dart';
import 'bloc/packs/packs_cubit.dart';
import 'bloc/pendientes/pendientes_cubit.dart';
import 'pages/captura_page.dart';
import 'pages/etiquetas_page.dart';
import 'pages/packs_page.dart';
import 'pages/pendientes_page.dart';
import 'pages/config_page.dart';
class PacketApp extends StatelessWidget {
const PacketApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AppCubit()..init()),
BlocProvider(create: (_) => CapturaCubit()),
BlocProvider(create: (_) => EtiquetasCubit()..init()),
BlocProvider(create: (_) => PacksCubit()..load()),
BlocProvider(create: (_) => PendientesCubit()..load()),
],
child: MaterialApp(
title: 'Packet',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
home: const MainScreen(),
debugShowCheckedModeBanner: false,
),
);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({super.key});
static const _pages = [
CapturaPage(),
EtiquetasPage(),
PacksPage(),
PendientesPage(),
ConfigPage(),
];
static const _titles = [
'Captura',
'Etiquetas',
'Packs',
'Pendientes',
'Config',
];
@override
Widget build(BuildContext context) {
return BlocBuilder<AppCubit, AppState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(_titles[state.currentIndex]),
actions: [
// Destino selector
if (state.destinos.isNotEmpty)
PopupMenuButton<int>(
onSelected: (id) {
final destino = state.destinos.firstWhere((d) => d.id == id);
context.read<AppCubit>().setDestinoActivo(destino);
},
itemBuilder: (_) => state.destinos
.map((d) => PopupMenuItem(
value: d.id,
child: Row(
children: [
Icon(
state.destinoActivo?.id == d.id
? Icons.check
: Icons.cloud,
size: 18,
),
const SizedBox(width: 8),
Text(d.nombre),
],
),
))
.toList(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
state.destinoActivo?.nombre ?? 'Sin destino',
style: Theme.of(context).textTheme.labelLarge,
),
const Icon(Icons.arrow_drop_down),
],
),
),
),
],
),
body: IndexedStack(
index: state.currentIndex,
children: _pages,
),
bottomNavigationBar: NavigationBar(
selectedIndex: state.currentIndex,
onDestinationSelected: (i) => context.read<AppCubit>().setIndex(i),
destinations: const [
NavigationDestination(
icon: Icon(Icons.camera_alt_outlined),
selectedIcon: Icon(Icons.camera_alt),
label: 'Captura',
),
NavigationDestination(
icon: Icon(Icons.label_outline),
selectedIcon: Icon(Icons.label),
label: 'Tags',
),
NavigationDestination(
icon: Icon(Icons.inventory_2_outlined),
selectedIcon: Icon(Icons.inventory_2),
label: 'Packs',
),
NavigationDestination(
icon: Icon(Icons.pending_outlined),
selectedIcon: Icon(Icons.pending),
label: 'Pend.',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Config',
),
],
),
);
},
);
}
}