144 lines
5.2 KiB
Dart
144 lines
5.2 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:please_pay_me/core/format/formatters.dart';
|
|
import 'package:please_pay_me/features/budgets/budgets_controller.dart';
|
|
import 'package:please_pay_me/features/expenses/expense_actions.dart';
|
|
import 'package:please_pay_me/features/journal/journal_controller.dart';
|
|
import 'package:please_pay_me/theme/theme.dart';
|
|
import 'package:please_pay_me/ui/ui.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
/// Operations grouped by day, with current-budget / all-budgets scope.
|
|
class JournalScreen extends StatelessWidget {
|
|
const JournalScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final journal = context.watch<JournalController>();
|
|
final budgets = context.watch<BudgetsController>();
|
|
|
|
return CupertinoPageScaffold(
|
|
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
|
|
child: CustomScrollView(
|
|
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
|
slivers: [
|
|
AppLargeNavBar(
|
|
title: 'Журнал',
|
|
trailing: CupertinoButton(
|
|
padding: EdgeInsets.zero,
|
|
minimumSize: Size.zero,
|
|
onPressed: budgets.hasBudgets
|
|
? () => showExpenseFormSheet(context: context, controller: budgets)
|
|
: null,
|
|
child: const AppIcon(CupertinoIcons.add_circled, color: AppColors.accent),
|
|
),
|
|
),
|
|
CupertinoSliverRefreshControl(onRefresh: () => journal.load(silent: true)),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: AppSpacing.s2, bottom: AppSpacing.s4),
|
|
child: AppSegmentedControl(
|
|
labels: JournalScope.values.map((scope) => scope.label).toList(),
|
|
index: JournalScope.values.indexOf(journal.scope),
|
|
onChanged: (index) => journal.setScope(JournalScope.values[index]),
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: journal.state.map(
|
|
loading: () => AppListSection(
|
|
children: List.generate(4, (_) => const AppSkeletonRow(hasLeading: false)),
|
|
),
|
|
error: (message) => AppErrorView(message: message, onRetry: journal.load),
|
|
data: (_) => _JournalBody(journal: journal),
|
|
),
|
|
),
|
|
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _JournalBody extends StatelessWidget {
|
|
const _JournalBody({required this.journal});
|
|
|
|
final JournalController journal;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final groups = journal.groups;
|
|
|
|
if (groups.isEmpty) {
|
|
final budgets = context.read<BudgetsController>();
|
|
return AppEmptyState(
|
|
icon: CupertinoIcons.doc_text,
|
|
title: 'Операций пока нет',
|
|
message: journal.scope == JournalScope.all
|
|
? 'Как только появится первая трата, она появится здесь.'
|
|
: 'В текущем бюджете ещё ничего не потрачено.',
|
|
actionLabel: budgets.hasBudgets ? 'Добавить трату' : null,
|
|
onAction: budgets.hasBudgets
|
|
? () => showExpenseFormSheet(context: context, controller: budgets)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
AppCard(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const AppText.footnote('Всего операций'),
|
|
AppText.title('${journal.state.valueOrNull?.totalCount ?? 0}'),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const AppText.footnote('Сумма'),
|
|
AppText.title(formatMoney(journal.totalSum)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.s5),
|
|
for (final group in groups) ...[
|
|
AppListSection(
|
|
header: formatRelativeDay(group.day),
|
|
footer: 'Итого за день: ${formatMoney(group.total)}',
|
|
children: [
|
|
for (final expense in group.items)
|
|
AppListTile(
|
|
title: expenseTitle(expense.note),
|
|
subtitle: formatWeekday(expense.spentAt),
|
|
value: expenseAmount(expense.amount),
|
|
showChevron: false,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.s5),
|
|
],
|
|
if (journal.hasMore)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter),
|
|
child: AppButton(
|
|
label: 'Показать ещё',
|
|
style: AppButtonStyle.gray,
|
|
loading: journal.isLoadingMore,
|
|
onPressed: journal.loadMore,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|