1
+ import 'package:flutter/material.dart' ;
2
+
3
+ class DetailsPage extends StatelessWidget {
4
+ const DetailsPage ({super .key});
5
+
6
+ @override
7
+ Widget build (BuildContext context) {
8
+ return Scaffold (
9
+ backgroundColor: const Color (0xFFF9F3FF ),
10
+ body: SafeArea (
11
+ child: SingleChildScrollView (
12
+ child: Column (
13
+ crossAxisAlignment: CrossAxisAlignment .start,
14
+ children: [
15
+ Stack (
16
+ children: [
17
+ Container (
18
+ height: 320 ,
19
+ width: double .infinity,
20
+ decoration: const BoxDecoration (
21
+ borderRadius: BorderRadius .only (
22
+ bottomLeft: Radius .circular (32 ),
23
+ bottomRight: Radius .circular (32 ),
24
+ ),
25
+ image: DecorationImage (
26
+ image: NetworkImage ('https://image.tmdb.org/t/p/w500/ochi.jpg' ),
27
+ fit: BoxFit .cover,
28
+ ),
29
+ ),
30
+ ),
31
+ Positioned (
32
+ top: 16 ,
33
+ left: 16 ,
34
+ child: _CircleButton (
35
+ icon: Icons .arrow_back,
36
+ onTap: () => Navigator .of (context).pop (),
37
+ ),
38
+ ),
39
+ Positioned (
40
+ top: 16 ,
41
+ right: 16 ,
42
+ child: _CircleButton (
43
+ icon: Icons .favorite_border,
44
+ onTap: () {},
45
+ ),
46
+ ),
47
+ Positioned (
48
+ left: 24 ,
49
+ bottom: 0 ,
50
+ child: _RatingIndicator (percent: 0.58 ),
51
+ ),
52
+ Positioned (
53
+ left: 100 ,
54
+ bottom: 32 ,
55
+ right: 16 ,
56
+ child: Column (
57
+ crossAxisAlignment: CrossAxisAlignment .start,
58
+ children: const [
59
+ Text (
60
+ 'The Legend of Ochi' ,
61
+ style: TextStyle (
62
+ color: Colors .black,
63
+ fontSize: 24 ,
64
+ fontWeight: FontWeight .bold,
65
+ ),
66
+ ),
67
+ SizedBox (height: 4 ),
68
+ Text (
69
+ '-' ,
70
+ style: TextStyle (
71
+ color: Colors .black54,
72
+ fontSize: 16 ,
73
+ ),
74
+ ),
75
+ ],
76
+ ),
77
+ ),
78
+ ],
79
+ ),
80
+ const SizedBox (height: 24 ),
81
+ const Padding (
82
+ padding: EdgeInsets .symmetric (horizontal: 16.0 ),
83
+ child: Text (
84
+ 'In a remote village on the island of Carpathia, a shy farm girl named Yuri is raised to fear an elusive animal species known as ochi. But when Yuri discovers a wounded baby ochi has been left behind, she escapes on a quest to bring him home.' ,
85
+ style: TextStyle (
86
+ color: Colors .black87,
87
+ fontSize: 16 ,
88
+ ),
89
+ ),
90
+ ),
91
+ const SizedBox (height: 24 ),
92
+ const Padding (
93
+ padding: EdgeInsets .symmetric (horizontal: 16.0 ),
94
+ child: Text (
95
+ 'Cast' ,
96
+ style: TextStyle (
97
+ color: Colors .black,
98
+ fontSize: 18 ,
99
+ fontWeight: FontWeight .bold,
100
+ ),
101
+ ),
102
+ ),
103
+ const SizedBox (height: 12 ),
104
+ SizedBox (
105
+ height: 90 ,
106
+ child: ListView (
107
+ scrollDirection: Axis .horizontal,
108
+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
109
+ children: const [
110
+ _CastCard (name: 'Helena Zengel' , image: 'https://randomuser.me/api/portraits/women/1.jpg' ),
111
+ _CastCard (name: 'Finn Wolfhard' , image: 'https://randomuser.me/api/portraits/men/2.jpg' ),
112
+ _CastCard (name: 'Emily Watson' , image: 'https://randomuser.me/api/portraits/women/3.jpg' ),
113
+ _CastCard (name: 'Willem Dafoe' , image: 'https://randomuser.me/api/portraits/men/4.jpg' ),
114
+ _CastCard (name: 'Razvan Stoica' , image: 'https://randomuser.me/api/portraits/men/5.jpg' ),
115
+ _CastCard (name: 'Carol B.' , image: 'https://randomuser.me/api/portraits/women/6.jpg' ),
116
+ ],
117
+ ),
118
+ ),
119
+ const SizedBox (height: 24 ),
120
+ const Padding (
121
+ padding: EdgeInsets .symmetric (horizontal: 16.0 ),
122
+ child: Text (
123
+ 'Categories' ,
124
+ style: TextStyle (
125
+ color: Colors .black,
126
+ fontSize: 16 ,
127
+ fontWeight: FontWeight .bold,
128
+ ),
129
+ ),
130
+ ),
131
+ const SizedBox (height: 12 ),
132
+ Padding (
133
+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
134
+ child: Wrap (
135
+ spacing: 8 ,
136
+ children: const [
137
+ _CategoryChip (label: 'Fantasy' ),
138
+ _CategoryChip (label: 'Adventure' ),
139
+ _CategoryChip (label: 'Family' ),
140
+ ],
141
+ ),
142
+ ),
143
+ const SizedBox (height: 24 ),
144
+ const Padding (
145
+ padding: EdgeInsets .symmetric (horizontal: 16.0 ),
146
+ child: Text (
147
+ 'Recommendations' ,
148
+ style: TextStyle (
149
+ color: Colors .black,
150
+ fontSize: 18 ,
151
+ fontWeight: FontWeight .bold,
152
+ ),
153
+ ),
154
+ ),
155
+ const SizedBox (height: 12 ),
156
+ SizedBox (
157
+ height: 180 ,
158
+ child: ListView (
159
+ scrollDirection: Axis .horizontal,
160
+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
161
+ children: const [
162
+ _RecommendationCard (image: 'https://image.tmdb.org/t/p/w500/rec1.jpg' ),
163
+ _RecommendationCard (image: 'https://image.tmdb.org/t/p/w500/rec2.jpg' ),
164
+ _RecommendationCard (image: 'https://image.tmdb.org/t/p/w500/rec3.jpg' ),
165
+ ],
166
+ ),
167
+ ),
168
+ const SizedBox (height: 32 ),
169
+ ],
170
+ ),
171
+ ),
172
+ ),
173
+ );
174
+ }
175
+ }
176
+
177
+ class _CircleButton extends StatelessWidget {
178
+ final IconData icon;
179
+ final VoidCallback onTap;
180
+ const _CircleButton ({required this .icon, required this .onTap});
181
+
182
+ @override
183
+ Widget build (BuildContext context) {
184
+ return Material (
185
+ color: Colors .white.withOpacity (0.7 ),
186
+ shape: const CircleBorder (),
187
+ child: InkWell (
188
+ customBorder: const CircleBorder (),
189
+ onTap: onTap,
190
+ child: Padding (
191
+ padding: const EdgeInsets .all (10.0 ),
192
+ child: Icon (icon, color: Colors .black, size: 24 ),
193
+ ),
194
+ ),
195
+ );
196
+ }
197
+ }
198
+
199
+ class _RatingIndicator extends StatelessWidget {
200
+ final double percent;
201
+ const _RatingIndicator ({required this .percent});
202
+
203
+ @override
204
+ Widget build (BuildContext context) {
205
+ return Column (
206
+ children: [
207
+ SizedBox (
208
+ width: 48 ,
209
+ height: 48 ,
210
+ child: Stack (
211
+ fit: StackFit .expand,
212
+ children: [
213
+ CircularProgressIndicator (
214
+ value: percent,
215
+ strokeWidth: 5 ,
216
+ backgroundColor: Colors .grey[300 ],
217
+ valueColor: const AlwaysStoppedAnimation <Color >(Color (0xFFB388F6 )),
218
+ ),
219
+ Center (
220
+ child: Text (
221
+ '${(percent * 100 ).round ()}%' ,
222
+ style: const TextStyle (
223
+ color: Color (0xFF7C4DFF ),
224
+ fontWeight: FontWeight .bold,
225
+ ),
226
+ ),
227
+ ),
228
+ ],
229
+ ),
230
+ ),
231
+ ],
232
+ );
233
+ }
234
+ }
235
+
236
+ class _CastCard extends StatelessWidget {
237
+ final String name;
238
+ final String image;
239
+ const _CastCard ({required this .name, required this .image});
240
+
241
+ @override
242
+ Widget build (BuildContext context) {
243
+ return Container (
244
+ width: 70 ,
245
+ margin: const EdgeInsets .only (right: 12 ),
246
+ child: Column (
247
+ children: [
248
+ CircleAvatar (
249
+ radius: 28 ,
250
+ backgroundImage: NetworkImage (image),
251
+ ),
252
+ const SizedBox (height: 4 ),
253
+ Text (
254
+ name,
255
+ style: const TextStyle (
256
+ color: Colors .black87,
257
+ fontSize: 12 ,
258
+ fontWeight: FontWeight .w500,
259
+ ),
260
+ maxLines: 1 ,
261
+ overflow: TextOverflow .ellipsis,
262
+ textAlign: TextAlign .center,
263
+ ),
264
+ ],
265
+ ),
266
+ );
267
+ }
268
+ }
269
+
270
+ class _CategoryChip extends StatelessWidget {
271
+ final String label;
272
+ const _CategoryChip ({required this .label});
273
+
274
+ @override
275
+ Widget build (BuildContext context) {
276
+ return Chip (
277
+ label: Text (label),
278
+ backgroundColor: Colors .white,
279
+ labelStyle: const TextStyle (
280
+ color: Colors .black87,
281
+ fontWeight: FontWeight .bold,
282
+ ),
283
+ side: const BorderSide (color: Colors .black12),
284
+ shape: RoundedRectangleBorder (
285
+ borderRadius: BorderRadius .circular (20 ),
286
+ ),
287
+ );
288
+ }
289
+ }
290
+
291
+ class _RecommendationCard extends StatelessWidget {
292
+ final String image;
293
+ const _RecommendationCard ({required this .image});
294
+
295
+ @override
296
+ Widget build (BuildContext context) {
297
+ return Container (
298
+ width: 120 ,
299
+ margin: const EdgeInsets .only (right: 16 ),
300
+ decoration: BoxDecoration (
301
+ borderRadius: BorderRadius .circular (16 ),
302
+ image: DecorationImage (
303
+ image: NetworkImage (image),
304
+ fit: BoxFit .cover,
305
+ ),
306
+ boxShadow: const [
307
+ BoxShadow (
308
+ color: Colors .black12,
309
+ blurRadius: 6 ,
310
+ offset: Offset (0 , 2 ),
311
+ ),
312
+ ],
313
+ ),
314
+ );
315
+ }
316
+ }
0 commit comments