Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/controller/iterator_controller.dart';
5 : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
6 : import 'package:app_finance/_classes/storage/app_data.dart';
7 : import 'package:app_finance/_classes/herald/app_locale.dart';
8 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
9 : import 'package:app_finance/_configs/screen_helper.dart';
10 : import 'package:app_finance/_configs/theme_helper.dart';
11 : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
12 : import 'package:app_finance/_ext/date_time_ext.dart';
13 : import 'package:app_finance/design/wrapper/background_wrapper.dart';
14 : import 'package:app_finance/pages/_interfaces/abstract_page_state.dart';
15 : import 'package:app_finance/pages/bill/widgets/bill_line_widget.dart';
16 : import 'package:app_finance/pages/bill/widgets/sliver_header_delegate.dart';
17 : import 'package:app_finance/design/generic/base_header_widget.dart';
18 : import 'package:app_finance/design/generic/base_swipe_widget.dart';
19 : import 'package:material_ui/material_ui.dart';
20 :
21 : class BillPage extends StatefulWidget {
22 2 : const BillPage({super.key});
23 :
24 0 : @override
25 0 : BillPageState createState() => BillPageState();
26 : }
27 :
28 : class BillPageState<T extends StatefulWidget> extends AbstractPageState<T> {
29 : InterfaceIterator? stream;
30 : List<Widget> itemsShown = [];
31 : DateTime timer = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day);
32 : final _scrollController = ScrollController();
33 : final batch = 25;
34 :
35 0 : @override
36 : void initState() {
37 0 : super.initState();
38 0 : _scrollController.addListener(() {
39 0 : if (_scrollController.position.extentAfter < 200 && !stream!.isFinished) {
40 0 : setState(() => _addItems());
41 : }
42 : });
43 : }
44 :
45 0 : @override
46 : void dispose() {
47 0 : _scrollController.dispose();
48 0 : super.dispose();
49 : }
50 :
51 0 : Widget addHeaderWidget() {
52 0 : final width = ScreenHelper.state().width - ThemeHelper.getIndent(4);
53 0 : DateTime startingDay = DateTime.now().getStartingDay(AppStartOfMonth.get());
54 0 : return SliverToBoxAdapter(
55 0 : child: BaseHeaderWidget(
56 : route: AppRoute.homeRoute,
57 0 : tooltip: AppLocale.labels.homeTooltip,
58 : width: width,
59 0 : total: state.getTotal(AppDataType.bills),
60 0 : title: '${AppLocale.labels.billHeadline}, ${startingDay.fullMonth()}',
61 : searchRoute: AppRoute.billSearchRoute,
62 : ),
63 : );
64 : }
65 :
66 0 : Widget addLineWidget(dynamic item) {
67 0 : final width = ScreenHelper.state().width - ThemeHelper.getIndent(4);
68 0 : final account = state.getByUuid(item.account);
69 0 : final budget = state.getByUuid(item.category);
70 0 : final countWidth = ThemeHelper.getWidthCount(null, context);
71 0 : return BaseSwipeWidget(
72 : routePath: AppRoute.billEditRoute,
73 0 : uuid: item.uuid!,
74 0 : child: BillLineWidget(
75 0 : uuid: item.uuid!,
76 : count: countWidth,
77 0 : title: item.title,
78 0 : description: account != null ? '${account.title} (${account.description})' : '',
79 0 : descriptionColor: account?.color ?? Colors.transparent,
80 0 : details: item.detailsFormatted,
81 0 : progress: item.progress,
82 0 : color: budget?.color ?? Colors.transparent,
83 0 : icon: budget?.icon ?? Icons.radio_button_unchecked_sharp,
84 0 : iconTooltip: budget?.title ?? '?',
85 0 : hidden: item.hidden,
86 : width: width,
87 : route: AppRoute.billViewRoute,
88 : ),
89 : );
90 : }
91 :
92 0 : List<dynamic> getItems(DateTime timer) {
93 0 : return stream!.getTill(0.0 + timer.millisecondsSinceEpoch);
94 : }
95 :
96 0 : void _addItems() {
97 0 : if (itemsShown.isEmpty) {
98 0 : itemsShown.add(addHeaderWidget());
99 : }
100 0 : if (stream == null || stream?.isFinished == true) {
101 : return;
102 : }
103 : String marker = '';
104 0 : List<dynamic> items = [];
105 : do {
106 0 : marker = timer.yMEd();
107 0 : items = getItems(timer);
108 0 : timer = timer.add(const Duration(days: -1));
109 0 : } while (items.isEmpty && !stream!.isFinished);
110 :
111 0 : itemsShown.add(
112 0 : SliverMainAxisGroup(
113 0 : slivers: [
114 0 : SliverPersistentHeader(
115 : pinned: true,
116 0 : delegate: SliverHeaderDelegate(marker),
117 : ),
118 0 : SliverPadding(
119 0 : padding: EdgeInsets.symmetric(vertical: ThemeHelper.getIndent(0.5)),
120 0 : sliver: SliverList.builder(
121 0 : itemCount: items.length,
122 0 : itemBuilder: (context, int index) {
123 0 : final item = items[index];
124 0 : return BackgroundWrapper(
125 : index: index,
126 0 : child: addLineWidget(item),
127 : );
128 : },
129 : ),
130 : ),
131 : ],
132 : ),
133 : );
134 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
135 0 : if (_scrollController.position.maxScrollExtent == 0) {
136 0 : setState(() => _addItems());
137 : }
138 : });
139 : }
140 :
141 0 : @override
142 0 : String getTitle() => AppLocale.labels.billHeadline;
143 :
144 0 : @override
145 0 : String getButtonName() => AppLocale.labels.addMainTooltip;
146 :
147 0 : @override
148 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
149 0 : NavigatorState nav = Navigator.of(context);
150 0 : return FloatingActionButton(
151 : heroTag: 'bill_page',
152 0 : onPressed: () => nav.pushNamed(AppRoute.billAddRoute),
153 0 : tooltip: getButtonName(),
154 : child: const Icon(Icons.add),
155 : );
156 : }
157 :
158 0 : InterfaceIterator getContentStream() => state.getStream<BillAppData>(AppDataType.bills);
159 :
160 0 : void clearState() {
161 0 : itemsShown = [];
162 0 : final now = DateTime.now();
163 0 : timer = DateTime(now.year, now.month, now.day);
164 0 : stream = null;
165 : }
166 :
167 0 : @override
168 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
169 0 : state.addListener(clearState);
170 0 : if (stream == null) {
171 0 : WidgetsBinding.instance.addPostFrameCallback(
172 0 : (_) => setState(() {
173 0 : stream = getContentStream();
174 0 : _addItems();
175 : }),
176 : );
177 : }
178 0 : return Padding(
179 0 : padding: EdgeInsets.all(ThemeHelper.getIndent()),
180 0 : child: CustomScrollView(
181 0 : controller: _scrollController,
182 0 : slivers: itemsShown,
183 : ),
184 : );
185 : }
186 : }
|