1+ import 'package:flutter/material.dart' ;
2+ import 'package:url_launcher/url_launcher.dart' ;
3+ import 'package:stashcard/card/carddetail.dart' ;
4+ import 'package:stashcard/card/cardlist.dart' ;
5+ import 'package:stashcard/models/enums.dart' ;
6+ import 'package:stashcard/providers/db.dart' ;
7+
8+
9+ class Home extends StatefulWidget {
10+ const Home ({super .key});
11+
12+ @override
13+ State <Home > createState () => _HomeState ();
14+ }
15+
16+ class _HomeState extends State <Home > {
17+ SortOptions selectedSort = SortOptions .byName;
18+ bool _isSearching = false ;
19+ TextEditingController _searchController = TextEditingController ();
20+ String searchQuery = '' ;
21+
22+
23+ @override
24+ void dispose () {
25+ _searchController.dispose ();
26+ super .dispose ();
27+ }
28+
29+ @override
30+ Widget build (BuildContext context) {
31+ const String title = 'Stashcard' ;
32+
33+ return Scaffold (
34+ appBar: AppBar (
35+ title: _isSearching ?
36+ TextField (
37+ controller: _searchController,
38+ autofocus: true ,
39+ decoration: const InputDecoration (
40+ hintText: 'Search...' ,
41+ border: InputBorder .none,
42+ ),
43+ onChanged: (value) {
44+ setState (() {
45+ searchQuery = value;
46+ });
47+ },
48+ )
49+ : Text (title),
50+ actions: [
51+ IconButton (
52+ onPressed: () {
53+ setState (() {
54+ _isSearching = ! _isSearching;
55+ if (! _isSearching) {
56+ _searchController.clear ();
57+ searchQuery = '' ;
58+ }
59+ });
60+ },
61+ icon: Icon (_isSearching ? Icons .close : Icons .search)
62+ ),
63+ IconButton (
64+ onPressed: () {
65+ showDialog (
66+ context: context,
67+ builder: (BuildContext context) => Theme (
68+ data: ThemeData .from (colorScheme: ColorScheme .of (context)),
69+ child: AlertDialog (
70+ title: const Text ("Donate" ),
71+ icon: const Icon (Icons .favorite),
72+ iconColor: Colors .red,
73+ content: Column (
74+ mainAxisSize: MainAxisSize .min,
75+ spacing: 10 ,
76+ children: [
77+ const Text (
78+ "I'm a student and I work on this app in my free time. If you like it, you can support development by donating. And if you don't want to donate, that's fine too." ,
79+ softWrap: true ,
80+ ),
81+ const Text ("Enjoy the app!" , style: TextStyle (fontWeight: FontWeight .bold, fontSize: 20 ),)
82+ ],
83+ ),
84+ actions: [
85+ FilledButton (
86+ onPressed: () async {
87+ try {
88+ await launchUrl (Uri .parse ("https://ko-fi.com/lahev" ));
89+ } catch (e) {
90+ if (context.mounted) {
91+ ScaffoldMessenger .of (context).showSnackBar (
92+ const SnackBar (content: Text ('Could not open donation link' )),
93+ );
94+ }
95+ }
96+ },
97+ child: const Text ('Donate' ),
98+ ),
99+ OutlinedButton (
100+ onPressed: () =>
101+ Navigator .pop (context),
102+ child: const Text ('Close' ),
103+ )
104+ ],
105+ )
106+ ));
107+ },
108+ icon: const Icon (Icons .favorite_border),
109+ ),
110+ PopupMenuButton <SortOptions >(
111+ initialValue: selectedSort,
112+ onSelected: (SortOptions sort) {
113+ setState (() {
114+ selectedSort = sort;
115+ });
116+ },
117+ itemBuilder: (BuildContext context) => < PopupMenuEntry <SortOptions >> [
118+ const PopupMenuItem (
119+ value: SortOptions .byName,
120+ child: Text ('Sort by name' )
121+ ),
122+ const PopupMenuItem (
123+ value: SortOptions .byDateCreated,
124+ child: Text ('Sort by date created' )
125+ ),
126+ const PopupMenuItem (
127+ value: SortOptions .byUsage,
128+ child: Text ('Sort by usage' )
129+ ),
130+ ],
131+ ),
132+ ],
133+ ),
134+ floatingActionButton: Builder (
135+ builder: (BuildContext context) {
136+ return FloatingActionButton (
137+ onPressed: () {
138+ Navigator .push (
139+ context,
140+ MaterialPageRoute (builder: (context) => const CardList ())
141+ );
142+ },
143+ child: const Icon (Icons .add),
144+ );
145+ },
146+ ),
147+ body: CardGrid (selectedOption: selectedSort, searchQuery: searchQuery,),
148+ );
149+ }
150+ }
151+
152+ class CardGrid extends StatefulWidget {
153+
154+ final SortOptions selectedOption;
155+ final String searchQuery;
156+
157+ const CardGrid ({super .key, required this .selectedOption, this .searchQuery = '' });
158+
159+ @override
160+ State <CardGrid > createState () => _CardGridState ();
161+ }
162+
163+ class _CardGridState extends State <CardGrid > {
164+ late Future <List <UserCard >> _futureCards;
165+ final db = DatabaseHelper ();
166+
167+ @override
168+ void initState () {
169+ super .initState ();
170+ _futureCards = _loadCards ();
171+ }
172+
173+ Future <List <UserCard >> _loadCards () async {
174+ return await db.getUserCardsSorted (widget.selectedOption);
175+ }
176+
177+ Future <void > _refreshCards () async {
178+ setState (() {
179+ _futureCards = _loadCards ();
180+ });
181+ }
182+
183+ @override
184+ void didUpdateWidget (covariant CardGrid oldWidget) {
185+ super .didUpdateWidget (oldWidget);
186+
187+ if (widget.selectedOption != oldWidget.selectedOption) {
188+ _refreshCards ();
189+ }
190+
191+ if (widget.searchQuery != oldWidget.searchQuery) {
192+ _refreshCards ();
193+ }
194+ }
195+
196+ @override
197+ Widget build (BuildContext context) {
198+ return FutureBuilder <List <UserCard >>(
199+ future: _futureCards,
200+ builder: (context, snapshot) {
201+ if (snapshot.hasError) {
202+ return const Center (child: Text ("Error loading cards" ));
203+ } else if (! snapshot.hasData || snapshot.data! .isEmpty) {
204+ return const Center (child: Text ("No cards found" ));
205+ }
206+
207+ final userCards = snapshot.data! ;
208+ final filteredCards = userCards.where ((userCard) {
209+ return widget.searchQuery.isEmpty || userCard.name.toLowerCase ().contains (widget.searchQuery.toLowerCase ());
210+ }).toList ();
211+
212+ return RefreshIndicator (
213+ onRefresh: () => _refreshCards (),
214+ child: GridView .builder (
215+ padding: const EdgeInsets .all (20 ),
216+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount (
217+ crossAxisCount: 2 ,
218+ crossAxisSpacing: 10 ,
219+ mainAxisSpacing: 10 ,
220+ childAspectRatio: 1.5 ,
221+ ),
222+ itemCount: filteredCards.length,
223+ itemBuilder: (context, index) {
224+ final userCard = filteredCards[index];
225+ return GestureDetector (
226+ onTap: () async {
227+ await Navigator .push (
228+ context,
229+ MaterialPageRoute (builder: (context) => CardDetail (cardId: userCard.id,))
230+ );
231+ if (userCard.id != null ) {
232+ db.incrementUsage (userCard.id! );
233+ }
234+ _refreshCards ();
235+ },
236+ child: Card (
237+ elevation: 2 ,
238+ child: Center (
239+ child: Text (userCard.name),
240+ ),
241+ ),
242+ );
243+ },
244+ ),
245+ );
246+ },
247+ );
248+ }
249+ }
0 commit comments