355 lines
14 KiB
Dart
355 lines
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(),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|