355 lines
No EOL
14 KiB
Dart
355 lines
No EOL
14 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import '../../core/utils/shared_provider.dart';
|
||
import '../pdf_export/pdf_service.dart';
|
||
import 'calculator_provider.dart';
|
||
import '../../widgets/custom_button.dart';
|
||
import '../converter/converter_provider.dart';
|
||
import '../splitter/splitter_provider.dart';
|
||
|
||
class MainNavigationScreen extends ConsumerStatefulWidget {
|
||
const MainNavigationScreen({super.key});
|
||
@override
|
||
ConsumerState<MainNavigationScreen> createState() => _MainNavigationScreenState();
|
||
}
|
||
|
||
class _MainNavigationScreenState extends ConsumerState<MainNavigationScreen> {
|
||
int _selectedIndex = 0;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final currentVal = ref.watch(sharedValueProvider);
|
||
final calcState = ref.watch(calculatorProvider);
|
||
final history = ref.watch(historyProvider);
|
||
|
||
return Scaffold(
|
||
backgroundColor: Colors.white,
|
||
appBar: AppBar(
|
||
title: const Text("Quote", style: TextStyle(fontWeight: FontWeight.bold)),
|
||
centerTitle: true,
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.picture_as_pdf_rounded),
|
||
onPressed: () {
|
||
PdfService.generateSplitterReport(
|
||
currentVal,
|
||
ref.read(splitterProvider).friends,
|
||
ref.read(splitterProvider.notifier).calculateSettlements(currentVal),
|
||
history,
|
||
);
|
||
},
|
||
)
|
||
],
|
||
),
|
||
body: Column(
|
||
children: [
|
||
Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: Colors.deepPurple.withOpacity(0.05),
|
||
border: Border(bottom: BorderSide(color: Colors.deepPurple.withOpacity(0.1))),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
Text(
|
||
_selectedIndex == 2 ? "LA TUA SPESA ATTUALE" : "VALORE ATTIVO",
|
||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.deepPurple)
|
||
),
|
||
FittedBox(
|
||
fit: BoxFit.scaleDown,
|
||
child: Text(
|
||
_selectedIndex == 0
|
||
? calcState.equation.replaceAll('.', ',')
|
||
: currentVal.toStringAsFixed(2).replaceAll('.', ','),
|
||
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.w900, color: Colors.black87),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Expanded(
|
||
child: IndexedStack(
|
||
index: _selectedIndex,
|
||
children: [
|
||
CalculatorModule(),
|
||
ConverterModule(),
|
||
SplitterModule(),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
bottomNavigationBar: NavigationBar(
|
||
selectedIndex: _selectedIndex,
|
||
onDestinationSelected: (index) => setState(() => _selectedIndex = index),
|
||
destinations: const [
|
||
NavigationDestination(icon: Icon(Icons.calculate_outlined), label: 'Calcola'),
|
||
NavigationDestination(icon: Icon(Icons.currency_exchange_outlined), label: 'Converti'),
|
||
NavigationDestination(icon: Icon(Icons.group_outlined), label: 'Dividi'),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class CalculatorModule extends ConsumerWidget {
|
||
const CalculatorModule({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final calcLogic = ref.read(calculatorProvider.notifier);
|
||
|
||
// Layout richiesto:
|
||
// Riga 1: C | ⌫ | % | ÷
|
||
// Riga 2: 7 | 8 | 9 | ×
|
||
// Riga 3: 4 | 5 | 6 | -
|
||
// Riga 4: 1 | 2 | 3 | +
|
||
// Riga 5: ± | 0 | , | =
|
||
|
||
final List<String> labels = [
|
||
'C', '⌫', '%', '÷',
|
||
'7', '8', '9', '×',
|
||
'4', '5', '6', '-',
|
||
'1', '2', '3', '+',
|
||
'±', '0', ',', '='
|
||
];
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: GridView.builder(
|
||
// Usiamo 4 colonne
|
||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 4,
|
||
mainAxisSpacing: 12,
|
||
crossAxisSpacing: 12,
|
||
),
|
||
itemCount: labels.length,
|
||
itemBuilder: (context, index) {
|
||
final label = labels[index];
|
||
|
||
// --- LOGICA COLORI ---
|
||
Color? bgColor;
|
||
Color textColor = Colors.black87;
|
||
|
||
if (RegExp(r'[0-9]').hasMatch(label) || label == ',') {
|
||
// Numeri e virgola: Arancione Chiaro
|
||
bgColor = const Color(0xFFFFCC80);
|
||
} else if (['÷', '×', '-', '+', '=', '%'].contains(label)) {
|
||
// Segni e Uguale: Azzurro
|
||
bgColor = const Color(0xFF81D4FA);
|
||
if (label == '=') bgColor = const Color(0xFF4FC3F7); // Azzurro più scuro per l'uguale
|
||
} else if (label == 'C' || label == '⌫') {
|
||
// Comandi di cancellazione: Rosso chiaro per distinguerli
|
||
bgColor = const Color(0xFFFFCDD2);
|
||
textColor = const Color(0xFFB71C1C);
|
||
} else if (label == '±') {
|
||
// Segno +/-: Grigio neutro
|
||
bgColor = Colors.grey[300];
|
||
}
|
||
|
||
return CustomButton(
|
||
label: label,
|
||
onPressed: () => calcLogic.onBtnPressed(label),
|
||
color: bgColor,
|
||
textColor: textColor,
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class ConverterModule extends StatelessWidget {
|
||
@override
|
||
Widget build(BuildContext context) => const Center(child: Text("Modulo Convertitore pronto"));
|
||
}
|
||
|
||
class SplitterModule extends ConsumerStatefulWidget {
|
||
const SplitterModule({super.key});
|
||
@override
|
||
ConsumerState<SplitterModule> createState() => _SplitterModuleState();
|
||
}
|
||
|
||
class _SplitterModuleState extends ConsumerState<SplitterModule> {
|
||
final TextEditingController _nameController = TextEditingController();
|
||
final TextEditingController _amountController = TextEditingController();
|
||
final TextEditingController _noteController = TextEditingController();
|
||
|
||
void _showHistoryDetails(BuildContext context, WidgetRef ref) {
|
||
showModalBottomSheet(
|
||
context: context,
|
||
isScrollControlled: true,
|
||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
|
||
builder: (context) {
|
||
return Consumer(builder: (context, ref, _) {
|
||
final history = ref.watch(historyProvider);
|
||
final historyLogic = ref.read(historyProvider.notifier);
|
||
|
||
return Padding(
|
||
padding: EdgeInsets.only(
|
||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||
left: 20, right: 20, top: 20
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Text("Dettaglio tua spesa", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||
const Text("Aggiungi una nota per ogni importo", style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||
const SizedBox(height: 15),
|
||
if (history.isEmpty)
|
||
const Padding(padding: EdgeInsets.all(20), child: Text("Nessun dato in calcolatrice")),
|
||
ConstrainedBox(
|
||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.4),
|
||
child: ListView.builder(
|
||
shrinkWrap: true,
|
||
itemCount: history.length,
|
||
itemBuilder: (context, idx) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child: Row(
|
||
children: [
|
||
Text("${history[idx].result} €", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||
const SizedBox(width: 15),
|
||
Expanded(
|
||
child: TextField(
|
||
decoration: const InputDecoration(hintText: "Nota...", isDense: true),
|
||
controller: TextEditingController.fromValue(
|
||
TextEditingValue(
|
||
text: history[idx].note,
|
||
selection: TextSelection.collapsed(offset: history[idx].note.length),
|
||
),
|
||
),
|
||
onChanged: (val) => historyLogic.updateNote(idx, val),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
const SizedBox(height: 20),
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 50)),
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text("Salva e Chiudi")
|
||
),
|
||
const SizedBox(height: 20),
|
||
],
|
||
),
|
||
);
|
||
});
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final mySpent = ref.watch(sharedValueProvider);
|
||
final splitter = ref.watch(splitterProvider);
|
||
final history = ref.watch(historyProvider);
|
||
final logic = ref.read(splitterProvider.notifier);
|
||
|
||
return Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(12.0),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(flex: 2, child: TextField(controller: _nameController, decoration: const InputDecoration(labelText: "Amico", isDense: true, border: OutlineInputBorder()))),
|
||
const SizedBox(width: 5),
|
||
Expanded(flex: 1, child: TextField(controller: _amountController, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: "€", isDense: true, border: OutlineInputBorder()))),
|
||
const SizedBox(width: 5),
|
||
Expanded(flex: 2, child: TextField(controller: _noteController, decoration: const InputDecoration(labelText: "Nota", isDense: true, border: OutlineInputBorder()))),
|
||
const SizedBox(width: 5),
|
||
IconButton.filled(
|
||
onPressed: () {
|
||
if (_nameController.text.isNotEmpty) {
|
||
double amount = double.tryParse(_amountController.text.replaceAll(',', '.')) ?? 0.0;
|
||
logic.addFriend(_nameController.text, amount, _noteController.text);
|
||
_nameController.clear(); _amountController.clear(); _noteController.clear();
|
||
FocusScope.of(context).unfocus();
|
||
}
|
||
},
|
||
icon: const Icon(Icons.add),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||
decoration: BoxDecoration(color: Colors.grey[50], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
|
||
child: CustomScrollView(
|
||
slivers: [
|
||
SliverToBoxAdapter(
|
||
child: Card(
|
||
elevation: 0,
|
||
color: Colors.deepPurple.withOpacity(0.05),
|
||
margin: const EdgeInsets.all(12),
|
||
child: InkWell(
|
||
onTap: () => _showHistoryDetails(context, ref),
|
||
borderRadius: BorderRadius.circular(12),
|
||
child: ListTile(
|
||
dense: true,
|
||
leading: const CircleAvatar(radius: 15, child: Icon(Icons.receipt_long, size: 16)),
|
||
title: const Text("Il Tuo Anticipo Totale", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.deepPurple)),
|
||
subtitle: Text("Clicca per i singoli calcoli (${history.length})"),
|
||
trailing: Text("${mySpent.toStringAsFixed(2)} €", style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.deepPurple, fontSize: 18)),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
SliverList(
|
||
delegate: SliverChildBuilderDelegate(
|
||
(context, index) {
|
||
final friend = splitter.friends[index];
|
||
return ListTile(
|
||
dense: true,
|
||
title: Text(friend.name, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||
subtitle: Text(friend.note.isEmpty ? "Anticipo spesa" : friend.note),
|
||
trailing: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text("${friend.spent.toStringAsFixed(2)} €"),
|
||
IconButton(icon: const Icon(Icons.remove_circle_outline, color: Colors.red, size: 18), onPressed: () => logic.removeFriend(index)),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
childCount: splitter.friends.length,
|
||
),
|
||
),
|
||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -5))],
|
||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: logic.calculateSettlements(mySpent).map((text) =>
|
||
Padding(padding: const EdgeInsets.symmetric(vertical: 2), child: Text(text, textAlign: TextAlign.center, style: TextStyle(
|
||
fontWeight: text.contains('➔') ? FontWeight.bold : FontWeight.normal,
|
||
color: text.contains('➔') ? Colors.deepPurple : Colors.black87,
|
||
fontSize: text.contains('➔') ? 16 : 13,
|
||
)))
|
||
).toList(),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
} |