import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:rental_income_tracker/models/monthly_rent_record.dart'; import 'package:rental_income_tracker/screens/settings_screen.dart'; import 'package:rental_income_tracker/state/rent_controller.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); Future _showStatusPicker( BuildContext context, PaymentStatus current, ) async { return showModalBottomSheet( context: context, builder: (context) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( title: const Text('Paid on time'), selected: current == PaymentStatus.onTime, onTap: () => Navigator.of(context).pop(PaymentStatus.onTime), ), ListTile( title: const Text('Paid late'), selected: current == PaymentStatus.late, onTap: () => Navigator.of(context).pop(PaymentStatus.late), ), ListTile( title: const Text('Not paid'), selected: current == PaymentStatus.notPaid, onTap: () => Navigator.of(context).pop(PaymentStatus.notPaid), ), ListTile( title: const Text('Not occupied'), selected: current == PaymentStatus.notOccupied, onTap: () => Navigator.of(context).pop(PaymentStatus.notOccupied), ), ListTile( title: const Text('Pending'), selected: current == PaymentStatus.pending, onTap: () => Navigator.of(context).pop(PaymentStatus.pending), ), ], ), ); }, ); } Future _showPaidUsdDialog( BuildContext context, { required double initialValue, }) async { final controller = TextEditingController( text: initialValue > 0 ? initialValue.toStringAsFixed(2) : '', ); return showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Enter paid amount (USD)'), content: TextField( controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'USD amount'), autofocus: true, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () { final value = double.tryParse(controller.text.trim()); if (value == null || value <= 0) { return; } Navigator.of(context).pop(value); }, child: const Text('Save'), ), ], ); }, ); } Future _handleStatusTap( BuildContext context, RentController controller, RentTableRow row, ) async { final nextStatus = await _showStatusPicker(context, row.status); if (!context.mounted) { return; } if (nextStatus == null || nextStatus == row.status) { return; } double? paidUsd; if (nextStatus == PaymentStatus.onTime) { paidUsd = controller.settings.rentUsd; } else if (nextStatus == PaymentStatus.late) { paidUsd = await _showPaidUsdDialog( context, initialValue: row.usdAmount ?? controller.settings.rentUsd, ); if (paidUsd == null) { return; } } await controller.setMonthStatusManually( year: row.year, month: row.month, status: nextStatus, paidUsd: paidUsd, ); if (!context.mounted) { return; } if (controller.errorMessage != null) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(controller.errorMessage!))); } } @override Widget build(BuildContext context) { return Consumer( builder: (context, controller, _) { final rows = controller.buildRowsForSelectedYear(); final usdCurrency = NumberFormat.currency( symbol: r'$', decimalDigits: 2, ); final sekCurrency = NumberFormat.currency( symbol: 'SEK ', decimalDigits: 2, ); final rateFormat = NumberFormat('0.000000'); final totalUsd = rows.isEmpty ? 0.0 : rows.last.runningUsd; final totalSek = rows.isEmpty ? 0.0 : rows.last.runningSek; return Scaffold( appBar: AppBar( title: const Text('Rental Income Tracker'), actions: [ IconButton( tooltip: 'Settings', onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const SettingsScreen(), ), ); }, icon: const Icon(Icons.settings), ), ], ), body: controller.isLoading ? const Center(child: CircularProgressIndicator()) : Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _YearSwitcher( year: controller.selectedYear, onPrevious: controller.canSelectPreviousYear ? () => controller.selectYear( controller.selectedYear - 1, ) : null, onNext: controller.selectedYear < DateTime.now().year ? () => controller.selectYear( controller.selectedYear + 1, ) : null, ), const SizedBox(height: 12), if (controller.errorMessage != null) Padding( padding: const EdgeInsets.only(bottom: 12), child: Text( controller.errorMessage!, style: TextStyle( color: Theme.of(context).colorScheme.error, ), ), ), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( child: DataTable( columns: const [ DataColumn(label: Text('Month')), DataColumn(label: Text('Status')), DataColumn(label: Text('USD')), DataColumn(label: Text('SEK')), DataColumn(label: Text('Rate USD->SEK')), DataColumn(label: Text('Running USD')), DataColumn(label: Text('Running SEK')), ], rows: rows.map((row) { return DataRow( cells: [ DataCell(Text(row.monthLabel)), DataCell( Row( children: [ _statusIcon(row.status), const SizedBox(width: 8), Text(_statusLabel(row.status)), const SizedBox(width: 8), const Icon(Icons.edit, size: 16), ], ), onTap: () async { await _handleStatusTap( context, controller, row, ); }, ), DataCell( Text( row.usdAmount == null ? '-' : usdCurrency.format(row.usdAmount), ), ), DataCell( Text( row.sekAmount == null ? '-' : sekCurrency.format(row.sekAmount), ), ), DataCell( Text( row.effectiveUsdToSekRate == null ? '-' : rateFormat.format( row.effectiveUsdToSekRate, ), ), ), DataCell( Text(usdCurrency.format(row.runningUsd)), ), DataCell( Text(sekCurrency.format(row.runningSek)), ), ], ); }).toList(), ), ), ), ), const SizedBox(height: 12), Text( 'Total: ${usdCurrency.format(totalUsd)} | ${sekCurrency.format(totalSek)}', style: Theme.of(context).textTheme.titleMedium, ), FilledButton.icon( onPressed: () async { await controller.markCurrentMonthPaidManually(); }, icon: const Icon(Icons.check_circle_outline), label: const Text('Mark current month as paid'), ), ], ), ), ); }, ); } Widget _statusIcon(PaymentStatus status) { switch (status) { case PaymentStatus.onTime: return const Icon(Icons.check_circle, color: Colors.green); case PaymentStatus.late: return const Icon(Icons.schedule, color: Colors.orange); case PaymentStatus.notPaid: return const Icon(Icons.cancel, color: Colors.red); case PaymentStatus.notOccupied: return const SizedBox.shrink(); case PaymentStatus.pending: return const Icon(Icons.hourglass_bottom, color: Colors.grey); } } String _statusLabel(PaymentStatus status) { switch (status) { case PaymentStatus.onTime: return 'Paid on time'; case PaymentStatus.late: return 'Paid late'; case PaymentStatus.notPaid: return 'Not paid'; case PaymentStatus.notOccupied: return 'Not occupied'; case PaymentStatus.pending: return 'Pending'; } } } class _YearSwitcher extends StatelessWidget { const _YearSwitcher({ required this.year, required this.onPrevious, required this.onNext, }); final int year; final VoidCallback? onPrevious; final VoidCallback? onNext; @override Widget build(BuildContext context) { return Row( children: [ IconButton(onPressed: onPrevious, icon: const Icon(Icons.chevron_left)), Text('$year', style: Theme.of(context).textTheme.titleLarge), IconButton(onPressed: onNext, icon: const Icon(Icons.chevron_right)), ], ); } }