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/_configs/theme_helper.dart';
6 : import 'package:app_finance/design/wrapper/background_wrapper.dart';
7 : import 'package:app_finance/design/wrapper/keep_alive_wrapper.dart';
8 : import 'package:material_ui/material_ui.dart';
9 :
10 : typedef FnListWidget = Widget Function(dynamic item, BuildContext context);
11 :
12 : class BaseListInfiniteWidget extends StatefulWidget {
13 : final InterfaceIterator? stream;
14 : final int batch;
15 : final double width;
16 : final FnListWidget buildListWidget;
17 :
18 0 : const BaseListInfiniteWidget({
19 : super.key,
20 : required this.stream,
21 : required this.width,
22 : required this.buildListWidget,
23 : this.batch = 25,
24 : });
25 :
26 0 : @override
27 0 : BaseListInfiniteWidgetState createState() => BaseListInfiniteWidgetState();
28 : }
29 :
30 : class BaseListInfiniteWidgetState extends State<BaseListInfiniteWidget> {
31 : final ScrollController scrollController = ScrollController();
32 : bool isLoading = false;
33 : List<dynamic> items = [];
34 :
35 0 : @override
36 : void initState() {
37 0 : super.initState();
38 0 : _loadItems();
39 0 : scrollController.addListener(_scrollListener);
40 : }
41 :
42 0 : @override
43 : void dispose() {
44 0 : scrollController.removeListener(_scrollListener);
45 0 : scrollController.dispose();
46 0 : super.dispose();
47 : }
48 :
49 0 : void _loadItems() {
50 0 : if (!mounted) {
51 : return;
52 : }
53 0 : setState(() => isLoading = true);
54 0 : Future.delayed(const Duration(milliseconds: 300), () {
55 0 : if (!mounted) {
56 : return;
57 : }
58 0 : setState(() {
59 0 : isLoading = false;
60 0 : _addItems();
61 : });
62 : });
63 : }
64 :
65 0 : void _addItems() {
66 0 : if (widget.stream != null) {
67 0 : items.addAll(widget.stream!.chunk(widget.batch));
68 : }
69 : }
70 :
71 0 : void _scrollListener() {
72 0 : if (scrollController.position.extentAfter < 200 &&
73 0 : !isLoading &&
74 0 : widget.stream != null &&
75 0 : !widget.stream!.isFinished) {
76 0 : _loadItems();
77 : }
78 : }
79 :
80 0 : void clearState() {
81 0 : if (!mounted) {
82 : return;
83 : }
84 0 : if (scrollController.hasClients) {
85 0 : scrollController.jumpTo(0);
86 0 : items.clear();
87 0 : setState(_addItems);
88 : }
89 : }
90 :
91 0 : @override
92 : Widget build(BuildContext context) {
93 0 : if (widget.stream?.isEmpty == true) {
94 : return ThemeHelper.emptyBox;
95 : }
96 0 : if (widget.stream?.isFirst == true) {
97 0 : WidgetsBinding.instance.addPostFrameCallback((_) => clearState());
98 : }
99 0 : return ListView.builder(
100 : scrollDirection: Axis.vertical,
101 : shrinkWrap: true,
102 0 : controller: scrollController,
103 0 : itemCount: items.length + 2,
104 0 : itemBuilder: (context, index) {
105 0 : if (index == 0) {
106 : return ThemeHelper.hIndent;
107 0 : } else if (index == items.length + 1) {
108 0 : if (isLoading) {
109 : return const Center(child: CircularProgressIndicator());
110 : } else {
111 : return ThemeHelper.formEndBox;
112 : }
113 : } else {
114 0 : return KeepAliveWrapper(
115 0 : child: BackgroundWrapper(
116 : index: index,
117 0 : child: widget.buildListWidget(
118 0 : items[index - 1],
119 : context,
120 : ),
121 : ),
122 : );
123 : }
124 : },
125 : );
126 : }
127 : }
|