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/herald/app_design.dart';
5 : import 'package:app_finance/_classes/storage/app_data.dart';
6 : import 'package:app_finance/_classes/herald/app_locale.dart';
7 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
8 : import 'package:app_finance/_classes/storage/app_preferences.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/components/_core/components_builder.dart';
13 : import 'package:app_finance/components/widgets/account_flow_chart.dart';
14 : import 'package:app_finance/components/widgets/budget_forecast_chart.dart';
15 : import 'package:app_finance/components/component_recent.dart';
16 : import 'package:app_finance/components/widgets/bill_ytd_chart.dart';
17 : import 'package:app_finance/pages/_interfaces/abstract_page_state.dart';
18 : import 'package:app_finance/pages/home/home_edit_page.dart';
19 : import 'package:app_finance/pages/start/start_page.dart';
20 : import 'package:app_finance/design/wrapper/grid_layer.dart';
21 : import 'package:app_finance/pages/home/widgets/init_tab.dart';
22 : import 'package:app_finance/design/button/toolbar_button_widget.dart';
23 : import 'package:app_finance/components/widgets/account_widget.dart';
24 : import 'package:app_finance/components/widgets/bill_widget.dart';
25 : import 'package:app_finance/components/widgets/budget_widget.dart';
26 : import 'package:app_finance/pages/home/widgets/goal_widget.dart';
27 : import 'package:flutter/foundation.dart';
28 : import 'package:flutter/material.dart';
29 : import 'package:flutter_markdown/flutter_markdown.dart';
30 : import 'package:flutter_svg/flutter_svg.dart';
31 : import 'package:intl/intl.dart' as intl;
32 : import 'package:package_info_plus/package_info_plus.dart';
33 : import 'package:provider/provider.dart';
34 :
35 : class HomePage extends StatefulWidget {
36 1 : const HomePage({super.key});
37 :
38 1 : @override
39 1 : HomePageState createState() => HomePageState();
40 : }
41 :
42 : class HomePageState extends AbstractPageState<HomePage> {
43 : String? toExpand;
44 : bool isEditMode = false;
45 : late String version;
46 :
47 1 : @override
48 : initState() {
49 1 : super.initState();
50 2 : toExpand = AppPreferences.get(AppPreferences.prefExpand);
51 2 : version = AppPreferences.get(AppPreferences.prefVersion) ?? '';
52 2 : PackageInfo.fromPlatform().then((PackageInfo value) {
53 0 : if (version != value.version) {
54 0 : WidgetsBinding.instance.addPostFrameCallback((_) => showModalBottomSheet(
55 0 : context: context,
56 0 : builder: (BuildContext context) => buildReleaseHelper(context, version),
57 : ));
58 0 : AppPreferences.set(AppPreferences.prefVersion, value.version);
59 : }
60 : });
61 : }
62 :
63 0 : Widget buildReleaseHelper(BuildContext context, String version) => buildHelper(
64 : context,
65 : type: 'upgrade',
66 0 : builder: (context, snapshot) {
67 0 : if (snapshot.hasData) {
68 0 : String data = snapshot.data!;
69 0 : if (version.isNotEmpty) {
70 0 : data = data.split('### $version')[0];
71 : }
72 0 : return Directionality(
73 0 : textDirection: AppDesign.getAlignment<TextDirection>(),
74 0 : child: Markdown(data: data),
75 : );
76 : }
77 0 : return Container();
78 : },
79 : );
80 :
81 0 : @override
82 0 : String getTitle() => AppLocale.labels.appTitle;
83 :
84 1 : @override
85 : Widget buildBottomBar(BuildContext context, BoxConstraints constraints) {
86 2 : return ThemeHelper.isWearable ? ThemeHelper.emptyBox : super.buildBottomBar(context, constraints);
87 : }
88 :
89 0 : @override
90 : Widget? buildRightBar(BuildContext context, BoxConstraints constraints) {
91 0 : return ThemeHelper.isWearable ? null : super.buildRightBar(context, constraints);
92 : }
93 :
94 1 : @override
95 : Widget? getBarLeading(NavigatorState nav) {
96 1 : if (ScreenHelper.state().isWide) {
97 : return ThemeHelper.emptyBox;
98 : }
99 1 : return Builder(
100 1 : builder: (BuildContext context) {
101 1 : return ToolbarButtonWidget(
102 : icon: Icons.menu,
103 : color: Colors.white70,
104 2 : tooltip: AppLocale.labels.navigationTooltip,
105 0 : onPressed: () => Scaffold.of(context).openDrawer(),
106 : );
107 : },
108 : );
109 : }
110 :
111 1 : @override
112 : List<Widget> getBarActions(NavigatorState nav) {
113 1 : final isWide = ScreenHelper.state().isWide;
114 1 : return [
115 1 : ToolbarButtonWidget(
116 : isWide: isWide,
117 : icon: Icons.app_registration_outlined,
118 : color: Colors.white70,
119 2 : tooltip: AppLocale.labels.customizeTooltip,
120 0 : onPressed: () => setState(() => isEditMode = true),
121 : ),
122 3 : if (![TargetPlatform.iOS, TargetPlatform.macOS, TargetPlatform.android].contains(defaultTargetPlatform))
123 0 : ToolbarButtonWidget(
124 : isWide: isWide,
125 : icon: Icons.switch_access_shortcut_add_outlined,
126 : color: Colors.white70,
127 0 : tooltip: AppLocale.labels.subscriptionTooltip,
128 0 : onPressed: () => nav.pushNamed(AppRoute.subscriptionRoute),
129 : ),
130 : ];
131 : }
132 :
133 1 : @override
134 : Widget getBarTitle(BuildContext context, [bool isBottom = false]) {
135 1 : return SvgPicture.asset(
136 : 'assets/images/fingrom.svg',
137 : height: isBottom ? 20 : 40,
138 : alignment: Alignment.centerLeft,
139 2 : semanticsLabel: AppLocale.labels.appTitle,
140 : );
141 : }
142 :
143 1 : @override
144 2 : String getButtonName() => AppLocale.labels.addMainTooltip;
145 :
146 1 : @override
147 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
148 1 : NavigatorState nav = Navigator.of(context);
149 1 : return FloatingActionButton(
150 : heroTag: 'home_page',
151 1 : mini: ThemeHelper.isWearable,
152 2 : onPressed: () => nav.pushNamed(AppRoute.billAddRoute),
153 1 : tooltip: getButtonName(),
154 : child: const Icon(Icons.add),
155 : );
156 : }
157 :
158 1 : @override
159 : Widget build(BuildContext context) {
160 2 : Provider.of<AppLocale>(context, listen: false).updateState(context);
161 1 : if (isEditMode) {
162 0 : return HomeEditPage(callback: () => setState(() => isEditMode = false));
163 : }
164 1 : if (AppPreferences.get(AppPreferences.prefPrivacyPolicy) == null) {
165 : return const StartPage();
166 : }
167 2 : return Consumer<AppData>(builder: (context, appState, _) {
168 1 : state = appState;
169 1 : if (appState.isLoading) {
170 : return const InitTab();
171 : }
172 1 : return super.build(context);
173 : });
174 : }
175 :
176 1 : @override
177 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
178 1 : final data = ComponentsBuilder.getData(context);
179 1 : if (data != null && data.isNotEmpty) {
180 0 : return ComponentsBuilder(data);
181 : }
182 1 : double indent = ThemeHelper.getIndent();
183 1 : EdgeInsets margin = EdgeInsets.only(top: indent);
184 1 : final countWidth = ThemeHelper.getWidthCount(constraints);
185 1 : final countHeight = ThemeHelper.getHeightCount(context, constraints);
186 1 : bool isVertical = countWidth == 1 && !ThemeHelper.isWearable;
187 1 : bool isWide = ScreenHelper.state().isWide;
188 1 : double width = ThemeHelper.getWidth(context, 3, constraints);
189 4 : double partWidth = width / countWidth - indent * (countWidth - 1);
190 :
191 1 : final billWidget = BillWidget(
192 : margin: margin,
193 7 : title: '${AppLocale.labels.billHeadline}, ${intl.DateFormat.MMMM(AppLocale.code).format(DateTime.now())}',
194 2 : state: state.get(AppDataType.bills),
195 : limit: 7,
196 : route: AppRoute.billRoute,
197 2 : tooltip: AppLocale.labels.billTooltip,
198 : width: isVertical ? width : partWidth,
199 : hasExpand: isVertical,
200 1 : toExpand: toExpand,
201 0 : callback: (v) => setState(() => toExpand = v),
202 : );
203 1 : final accountWidget = AccountWidget(
204 : margin: margin,
205 5 : title: '${AppLocale.labels.accountHeadline}, ${AppLocale.labels.total}',
206 2 : state: state.get(AppDataType.accounts),
207 : limit: 7,
208 : route: AppRoute.accountRoute,
209 2 : tooltip: AppLocale.labels.accountTooltip,
210 : width: isVertical ? width : partWidth,
211 : hasExpand: isVertical,
212 1 : toExpand: toExpand,
213 0 : callback: (v) => setState(() => toExpand = v),
214 3 : )..exchange = Exchange(store: super.state);
215 1 : final budgetWidget = BudgetWidget(
216 : margin: margin,
217 5 : title: '${AppLocale.labels.budgetHeadline}, ${AppLocale.labels.left}',
218 2 : state: state.get(AppDataType.budgets),
219 : limit: 7,
220 : route: AppRoute.budgetRoute,
221 2 : tooltip: AppLocale.labels.budgetTooltip,
222 : width: isVertical ? width : partWidth,
223 : hasExpand: isVertical,
224 1 : toExpand: toExpand,
225 0 : callback: (v) => setState(() => toExpand = v),
226 3 : )..exchange = Exchange(store: super.state);
227 :
228 1 : return GridLayer(
229 : padding: indent,
230 : width: width,
231 : crossAxisCount: countWidth,
232 : strategy: switch (countWidth) {
233 1 : 4 => [
234 0 : [0, 6],
235 0 : [2, 5],
236 0 : [3, 4],
237 0 : [1, 7]
238 : ],
239 1 : 3 => [
240 0 : [2],
241 0 : [3],
242 0 : [0, 1]
243 : ],
244 2 : 2 => [
245 1 : [2, 3],
246 1 : [0, 1]
247 : ],
248 0 : _ => [
249 0 : [0, 1, 2, 3]
250 : ]
251 : },
252 1 : children: [
253 1 : countHeight > 3
254 : ? isWide
255 0 : ? Expanded(
256 0 : child: Padding(
257 0 : padding: EdgeInsets.only(top: indent / 2),
258 : child: const ComponentRecent({'type': ComponentRecentType.goals, 'count': 7}),
259 : ),
260 : )
261 1 : : GoalWidget(
262 : width: isVertical ? width : partWidth,
263 2 : state: super.state.getList(AppDataType.goals),
264 : )
265 : : ThemeHelper.emptyBox,
266 : billWidget,
267 : accountWidget,
268 : budgetWidget,
269 0 : () => const Expanded(
270 : child: Column(
271 : children: [
272 : Expanded(child: ThemeHelper.emptyBox),
273 : AccountFlowChart(),
274 : ThemeHelper.hIndent6x,
275 : ],
276 : ),
277 : ),
278 0 : () => const Expanded(
279 : child: Column(
280 : children: [
281 : Expanded(child: ThemeHelper.emptyBox),
282 : BudgetForecastChart(),
283 : ThemeHelper.hIndent6x,
284 : ],
285 : ),
286 : ),
287 0 : () => const Expanded(
288 : child: Column(
289 : children: [
290 : Expanded(child: ThemeHelper.emptyBox),
291 : BillYtdChart(),
292 : ThemeHelper.hIndent6x,
293 : ],
294 : ),
295 : ),
296 0 : () => const Expanded(
297 : child: ComponentRecent({'type': ComponentRecentType.invoice, 'count': 7}),
298 : ),
299 : ],
300 : );
301 : }
302 : }
|