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/purchase_controller.dart';
5 : import 'package:app_finance/_classes/herald/app_design.dart';
6 : import 'package:app_finance/_classes/herald/app_locale.dart';
7 : import 'package:app_finance/_configs/custom_text_theme.dart';
8 : import 'package:app_finance/_configs/theme_helper.dart';
9 : import 'package:app_finance/_ext/build_context_ext.dart';
10 : import 'package:app_finance/design/generic/loading_widget.dart';
11 : import 'package:app_finance/design/generic/notification_bar.dart';
12 : import 'package:app_finance/design/wrapper/row_widget.dart';
13 : import 'package:app_finance/design/wrapper/text_wrapper.dart';
14 : import 'package:flutter/material.dart';
15 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
16 : import 'package:flutter_grid_layout/flutter_grid_layout.dart';
17 : import 'package:in_app_purchase/in_app_purchase.dart';
18 :
19 : class PurchaseWidget extends StatefulWidget {
20 : final String path;
21 : final ProductDetails? product;
22 : final List<PurchaseDetails>? purchase;
23 : final double size;
24 : final String title;
25 :
26 0 : const PurchaseWidget(this.path, {super.key, required this.title, this.product, this.purchase, this.size = 150});
27 :
28 0 : @override
29 0 : PurchaseWidgetState createState() => PurchaseWidgetState();
30 : }
31 :
32 : class PurchaseWidgetState extends State<PurchaseWidget> {
33 : bool isLoading = false;
34 :
35 0 : Future<void> _purchase() async {
36 0 : if (widget.product == null) {
37 0 : NotificationBar.showSnackBar(context, AppLocale.labels.subscriptionInactive, true);
38 : } else {
39 0 : setState(() => isLoading = true);
40 0 : await PurchaseController.buy(widget.product!);
41 0 : setState(() => isLoading = false);
42 : }
43 : }
44 :
45 0 : @override
46 : Widget build(BuildContext context) {
47 0 : final indent = ThemeHelper.getIndent(0.5);
48 0 : final cost = widget.product?.rawPrice.toCurrency(code: widget.product?.currencyCode, withPattern: false) ?? '---';
49 0 : final costWidth = ThemeHelper.getTextWidth(Text(cost));
50 0 : return InkWell(
51 0 : onTap: _purchase,
52 0 : child: Container(
53 0 : width: widget.size,
54 0 : height: widget.size,
55 0 : margin: EdgeInsets.all(indent),
56 0 : decoration: BoxDecoration(
57 0 : color: context.colorScheme.surface,
58 0 : borderRadius: BorderRadius.circular(3),
59 0 : boxShadow: [
60 0 : BoxShadow(
61 0 : color: context.colorScheme.secondary.withOpacity(0.1),
62 0 : spreadRadius: indent / 2,
63 : blurRadius: indent,
64 0 : offset: Offset(0, indent / 2),
65 : ),
66 : ],
67 : ),
68 0 : child: isLoading
69 0 : ? LoadingWidget(isLoading: isLoading, size: Size(widget.size, widget.size))
70 0 : : GridContainer(
71 0 : alignment: AppDesign.getAlignment<MainAxisAlignment>(),
72 : rows: const [40, null, 40],
73 : columns: const [40, null, 34],
74 0 : children: [
75 0 : GridItem(
76 : start: const Size(0, 0),
77 : end: const Size(4, 4),
78 0 : child: SizedBox(
79 0 : child: ClipPath(
80 0 : clipper: _TopRightClipper(),
81 0 : child: Image.asset(widget.path),
82 : ),
83 : ),
84 : ),
85 0 : GridItem(
86 : start: const Size(2, 0),
87 : end: const Size(3, 1),
88 0 : child: Icon(
89 0 : switch (widget.purchase?.last.status) {
90 0 : PurchaseStatus.purchased => Icons.check,
91 0 : PurchaseStatus.pending => Icons.pending,
92 : _ => Icons.add
93 : },
94 0 : color: context.colorScheme.secondary.withOpacity(0.3),
95 : ),
96 : ),
97 0 : GridItem(
98 : start: const Size(0, 2),
99 : end: const Size(3, 3),
100 : order: 2,
101 0 : child: Container(
102 0 : color: context.colorScheme.surface.withOpacity(0.9),
103 0 : padding: EdgeInsets.all(indent),
104 0 : child: RowWidget(
105 0 : chunk: [null, costWidth],
106 : indent: indent,
107 : alignment: MainAxisAlignment.spaceBetween,
108 0 : maxWidth: widget.size - indent * 2,
109 0 : children: [
110 0 : [
111 0 : Align(
112 : alignment: Alignment.centerLeft,
113 0 : child: TextWrapper(widget.title, maxLines: 2, style: context.textTheme.bodySmall)),
114 : ],
115 0 : [
116 0 : Align(
117 : alignment: Alignment.centerRight,
118 0 : child: TextWrapper(cost, style: context.textTheme.numberMedium),
119 : ),
120 : ],
121 : ],
122 : )),
123 : ),
124 : ],
125 : ),
126 : ),
127 : );
128 : }
129 : }
130 :
131 : class _TopRightClipper extends CustomClipper<Path> {
132 0 : @override
133 : Path getClip(Size size) {
134 0 : final path = Path()
135 0 : ..moveTo(size.width / 3, 0)
136 0 : ..quadraticBezierTo(size.width * 3 / 4, 0, size.width, size.height / 2.5)
137 0 : ..lineTo(size.width, size.height)
138 0 : ..lineTo(0, size.height)
139 0 : ..lineTo(0, 0);
140 :
141 : return path;
142 : }
143 :
144 0 : @override
145 : bool shouldReclip(CustomClipper<Path> oldClipper) => false;
146 : }
|