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>
This commit is contained in:
147
lib/presentation/app.dart
Normal file
147
lib/presentation/app.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
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',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user