11import 'package:flutter/material.dart' ;
2+ import '../component/graphql_client.dart' ;
3+ import 'package:google_fonts/google_fonts.dart' ;
4+
5+ /// Theme color definitions
6+ class AppColors {
7+ static const Color primaryGreen = Color (0xFFABF4D0 );
8+ static const Color darkGreen = Color (0xFF037549 );
9+ static const Color lightGreen = ColoAr (0xFF28DF99 );
10+ static const Color primaryBrown = Color (0xFF3E2723 );
11+ static const Color lightBrown = Color (0xFFD0C38F );
12+ static const Color lightCream = Color (0xFFF8E6C8 );
13+ static const Color lightYellow = Color (0xFFFFDBAD );
14+ }
15+
16+ /// Layout spacing and sizing
17+ class AppDimensions {
18+ static const double defaultPadding = 24.0 ;
19+ static const double smallSpacing = 8.0 ;
20+ static const double mediumSpacing = 16.0 ;
21+ static const double largeSpacing = 40.0 ;
22+ static const double buttonHeight = 50.0 ;
23+ static const double borderRadius = 8.0 ;
24+ static const double bookLogoWidth = 225.0 ;
25+ static const double bookLogoHeight = 284.0 ;
26+ }
27+
28+ /// Typography styles
29+ class AppStyles {
30+ static TextStyle get titleStyle => GoogleFonts .nanumPenScript (
31+ fontSize: 40 ,
32+ color: Colors .black,
33+ );
34+
35+ static TextStyle get headerStyle => const TextStyle (
36+ fontSize: 24 ,
37+ fontWeight: FontWeight .bold,
38+ );
39+
40+ static TextStyle get subHeaderStyle => const TextStyle (
41+ fontSize: 20 ,
42+ fontWeight: FontWeight .bold,
43+ );
44+
45+ static TextStyle get bodyStyle => const TextStyle (
46+ fontSize: 18 ,
47+ );
48+
49+ static TextStyle get labelStyle => const TextStyle (
50+ fontSize: 16 ,
51+ fontWeight: FontWeight .bold,
52+ );
53+
54+ static TextStyle get captionStyle => const TextStyle (
55+ fontSize: 12 ,
56+ color: Colors .black54,
57+ );
58+ }
59+
60+ // -------------------------------
61+ // Common Reusable Widgets
62+ // -------------------------------
63+ class CommonWidgets {
64+ /// Book Logo with title overlay
65+ static Widget buildBookLogo () {
66+ return Stack (
67+ alignment: Alignment .center,
68+ children: [
69+ Image .asset (
70+ 'assets/images/book.png' ,
71+ fit: BoxFit .contain,
72+ width: AppDimensions .bookLogoWidth,
73+ height: AppDimensions .bookLogoHeight,
74+ ),
75+ Positioned (
76+ child: Text (
77+ '오늘의 책' ,
78+ style: AppStyles .titleStyle,
79+ textAlign: TextAlign .center,
80+ ),
81+ ),
82+ ],
83+ );
84+ }
85+
86+ /// Custom text input field
87+ static Widget buildInputField ({
88+ required String label,
89+ required TextEditingController controller,
90+ String ? hintText,
91+ bool isPassword = false ,
92+ bool enabled = true ,
93+ }) {
94+ return Column (
95+ crossAxisAlignment: CrossAxisAlignment .start,
96+ children: [
97+ Text (label, style: AppStyles .labelStyle),
98+ const SizedBox (height: AppDimensions .smallSpacing),
99+ Container (
100+ decoration: BoxDecoration (
101+ color: AppColors .lightBrown,
102+ borderRadius: BorderRadius .circular (AppDimensions .borderRadius),
103+ ),
104+ child: TextField (
105+ controller: controller,
106+ obscureText: isPassword,
107+ enabled: enabled,
108+ decoration: InputDecoration (
109+ hintText: hintText,
110+ contentPadding: const EdgeInsets .symmetric (
111+ horizontal: AppDimensions .mediumSpacing,
112+ vertical: 12 ,
113+ ),
114+ border: InputBorder .none,
115+ ),
116+ ),
117+ ),
118+ ],
119+ );
120+ }
121+
122+ static Widget buildPrimaryButton ({
123+ required String text,
124+ required VoidCallback ? onPressed,
125+ Color backgroundColor = AppColors .primaryBrown,
126+ Color foregroundColor = Colors .white,
127+ bool isLoading = false ,
128+ double ? width,
129+ }) {
130+ return ElevatedButton (
131+ onPressed: onPressed,
132+ style: ElevatedButton .styleFrom (
133+ backgroundColor: backgroundColor,
134+ foregroundColor: foregroundColor,
135+ minimumSize: Size (width ?? double .infinity, AppDimensions .buttonHeight),
136+ shape: RoundedRectangleBorder (
137+ borderRadius: BorderRadius .circular (AppDimensions .borderRadius),
138+ ),
139+ ),
140+ child: isLoading
141+ ? const SizedBox (
142+ height: 20 ,
143+ width: 20 ,
144+ child: CircularProgressIndicator (
145+ strokeWidth: 2 ,
146+ valueColor: AlwaysStoppedAnimation <Color >(Colors .white),
147+ ),
148+ )
149+ : Text (text),
150+ );
151+ }
152+
153+ static Widget buildSecondaryButton ({
154+ required String text,
155+ required VoidCallback ? onPressed,
156+ double ? width,
157+ }) {
158+ return ElevatedButton (
159+ onPressed: onPressed,
160+ style: ElevatedButton .styleFrom (
161+ backgroundColor: AppColors .lightBrown,
162+ foregroundColor: Colors .black,
163+ minimumSize: Size (width ?? double .infinity, AppDimensions .buttonHeight),
164+ shape: RoundedRectangleBorder (
165+ borderRadius: BorderRadius .circular (AppDimensions .borderRadius),
166+ ),
167+ ),
168+ child: Text (text),
169+ );
170+ }
171+
172+ static Widget buildNextButton ({
173+ required VoidCallback onPressed,
174+ }) {
175+ return Align (
176+ alignment: Alignment .centerRight,
177+ child: ElevatedButton (
178+ onPressed: onPressed,
179+ style: ElevatedButton .styleFrom (
180+ backgroundColor: AppColors .lightCream,
181+ foregroundColor: Colors .black,
182+ minimumSize: const Size (100 , 45 ),
183+ shape: RoundedRectangleBorder (
184+ borderRadius: BorderRadius .circular (AppDimensions .borderRadius),
185+ ),
186+ ),
187+ child: const Text ("다음" ),
188+ ),
189+ );
190+ }
191+
192+ static AppBar buildAppBar ({VoidCallback ? onBackPressed}) {
193+ return AppBar (
194+ backgroundColor: Colors .transparent,
195+ elevation: 0 ,
196+ leading: IconButton (
197+ icon: const Icon (Icons .arrow_back, color: Colors .black),
198+ onPressed: onBackPressed,
199+ ),
200+ );
201+ }
202+
203+ static void showSnackBar (BuildContext context, String message,
204+ {bool isError = false }) {
205+ ScaffoldMessenger .of (context).showSnackBar (
206+ SnackBar (
207+ content: Text (message),
208+ backgroundColor: isError ? Colors .red : Colors .green,
209+ ),
210+ );
211+ }
212+ }
2213
3214Widget buildBookCover (String ? coverUrl, double x, double y) {
4215 return ClipRRect (
@@ -15,4 +226,44 @@ Widget buildBookCover(String? coverUrl, double x, double y) {
15226 )
16227 : const Icon (Icons .book, size: 100 ),
17228 );
18- }
229+ }
230+
231+ class BookmarksProvider extends ChangeNotifier {
232+ final List <String > _bookmarkedBooks = [];
233+ final List <String > _excludedBookmarked = [];
234+
235+ List <String > get bookmarkedBooks => _bookmarkedBooks;
236+
237+ Future <void > addBookmark (String bookId) async {
238+ _bookmarkedBooks.add (bookId);
239+ await _updateShelf ();
240+ notifyListeners ();
241+ }
242+
243+ Future <void > removeBookmark (String bookId) async {
244+ _bookmarkedBooks.remove (bookId);
245+ _excludedBookmarked.add (bookId);
246+ await _updateShelf ();
247+ notifyListeners ();
248+ }
249+
250+ Future <void > _updateShelf () async {
251+ try {
252+ debugPrint (_bookmarkedBooks.toString ());
253+ debugPrint (_excludedBookmarked.toString ());
254+ final shelfResult = await GraphQLService .updateShelf (
255+ 'default' , _bookmarkedBooks, _excludedBookmarked);
256+ if (shelfResult != null ) {
257+ debugPrint ('Default shelf updated successfully' );
258+ } else {
259+ debugPrint ('Failed to update default shelf' );
260+ }
261+ } catch (e) {
262+ debugPrint ('Error update default shelf: $e ' );
263+ }
264+ }
265+
266+ bool isBookmarked (String bookId) {
267+ return _bookmarkedBooks.contains (bookId);
268+ }
269+ }
0 commit comments