@@ -7,6 +7,7 @@ import 'package:shareloop/screens/edit_item_screen.dart';
77import 'package:shareloop/screens/login_screen.dart' ;
88import 'package:shareloop/screens/rent_request_chat_screen.dart' ;
99import 'package:shareloop/state/auth.dart' show authProvider;
10+ import 'package:shareloop/state/booked_dates.dart' ;
1011import 'package:shareloop/state/item_detail.dart' ;
1112
1213class ItemScreen extends ConsumerWidget {
@@ -40,6 +41,7 @@ class ItemScreen extends ConsumerWidget {
4041 ),
4142 body: asyncItem.when (
4243 data: (item) => _Content (
44+ itemId: itemId,
4345 item: item,
4446 userId: asyncUser.hasValue ? asyncUser.value? .id : null ,
4547 ),
@@ -50,15 +52,22 @@ class ItemScreen extends ConsumerWidget {
5052 }
5153}
5254
53- class _Content extends StatelessWidget {
55+ class _Content extends ConsumerWidget {
56+ final int itemId;
5457 final ItemDetail item;
5558 final int ? userId;
5659
57- const _Content ({required this .item, required this .userId});
60+ const _Content ({
61+ required this .itemId,
62+ required this .item,
63+ required this .userId,
64+ });
5865
5966 @override
60- Widget build (BuildContext context) {
67+ Widget build (BuildContext context, WidgetRef ref ) {
6168 final isOwnItem = userId != null && item.author.id == userId;
69+ final asyncBooked = ref.watch (bookedDatesProvider (itemId));
70+
6271 return SingleChildScrollView (
6372 child: Column (
6473 crossAxisAlignment: CrossAxisAlignment .start,
@@ -118,6 +127,24 @@ class _Content extends StatelessWidget {
118127 ),
119128 ),
120129 ],
130+ const SizedBox (height: 24 ),
131+ Text (
132+ 'Verfügbarkeit' ,
133+ style: Theme .of (context).textTheme.titleMedium,
134+ ),
135+ const SizedBox (height: 8 ),
136+ asyncBooked.when (
137+ data: (booked) => _AvailabilityCalendar (
138+ bookedRanges: booked,
139+ ),
140+ loading: () => const SizedBox (
141+ height: 300 ,
142+ child: Center (child: CircularProgressIndicator ()),
143+ ),
144+ error: (_, __) => const Text (
145+ 'Verfügbarkeit konnte nicht geladen werden' ,
146+ ),
147+ ),
121148 const SizedBox (height: 32 ),
122149 const Divider (),
123150 const SizedBox (height: 16 ),
@@ -131,6 +158,195 @@ class _Content extends StatelessWidget {
131158 }
132159}
133160
161+ class _AvailabilityCalendar extends StatefulWidget {
162+ final List <DateRange > bookedRanges;
163+
164+ const _AvailabilityCalendar ({required this .bookedRanges});
165+
166+ @override
167+ State <_AvailabilityCalendar > createState () => _AvailabilityCalendarState ();
168+ }
169+
170+ class _AvailabilityCalendarState extends State <_AvailabilityCalendar > {
171+ late DateTime _currentMonth;
172+
173+ @override
174+ void initState () {
175+ super .initState ();
176+ _currentMonth = DateTime .now ();
177+ }
178+
179+ @override
180+ Widget build (BuildContext context) {
181+ final now = DateTime .now ();
182+ return Card (
183+ child: Padding (
184+ padding: const EdgeInsets .all (8 ),
185+ child: Column (
186+ children: [
187+ _MonthHeader (
188+ currentMonth: _currentMonth,
189+ onPrevious: _currentMonth.month == now.month &&
190+ _currentMonth.year == now.year
191+ ? null
192+ : () => setState (() {
193+ _currentMonth = DateTime (
194+ _currentMonth.year,
195+ _currentMonth.month - 1 ,
196+ );
197+ }),
198+ onNext: () => setState (() {
199+ _currentMonth = DateTime (
200+ _currentMonth.year,
201+ _currentMonth.month + 1 ,
202+ );
203+ }),
204+ ),
205+ _CalendarGrid (
206+ currentMonth: _currentMonth,
207+ bookedRanges: widget.bookedRanges,
208+ ),
209+ ],
210+ ),
211+ ),
212+ );
213+ }
214+ }
215+
216+ class _MonthHeader extends StatelessWidget {
217+ final DateTime currentMonth;
218+ final VoidCallback ? onPrevious;
219+ final VoidCallback onNext;
220+
221+ const _MonthHeader ({
222+ required this .currentMonth,
223+ required this .onPrevious,
224+ required this .onNext,
225+ });
226+
227+ @override
228+ Widget build (BuildContext context) {
229+ final loc = MaterialLocalizations .of (context);
230+ return Row (
231+ mainAxisAlignment: MainAxisAlignment .spaceBetween,
232+ children: [
233+ IconButton (
234+ icon: const Icon (Icons .chevron_left),
235+ onPressed: onPrevious,
236+ ),
237+ Text (
238+ loc.formatMonthYear (currentMonth),
239+ style: Theme .of (context).textTheme.titleSmall,
240+ ),
241+ IconButton (
242+ icon: const Icon (Icons .chevron_right),
243+ onPressed: onNext,
244+ ),
245+ ],
246+ );
247+ }
248+ }
249+
250+ class _CalendarGrid extends StatelessWidget {
251+ final DateTime currentMonth;
252+ final List <DateRange > bookedRanges;
253+
254+ const _CalendarGrid ({
255+ required this .currentMonth,
256+ required this .bookedRanges,
257+ });
258+
259+ bool _isBooked (DateTime day) {
260+ for (final r in bookedRanges) {
261+ final start = DateTime (r.startDate.year, r.startDate.month, r.startDate.day);
262+ final end = DateTime (r.endDate.year, r.endDate.month, r.endDate.day);
263+ if (! day.isBefore (start) && ! day.isAfter (end)) return true ;
264+ }
265+ return false ;
266+ }
267+
268+ @override
269+ Widget build (BuildContext context) {
270+ final cs = Theme .of (context).colorScheme;
271+ final loc = MaterialLocalizations .of (context);
272+ final now = DateTime .now ();
273+ final today = DateTime (now.year, now.month, now.day);
274+
275+ final firstDayOfMonth = DateTime (currentMonth.year, currentMonth.month, 1 );
276+ final lastDayOfMonth = DateTime (currentMonth.year, currentMonth.month + 1 , 0 );
277+ final weekdayOffset = loc.firstDayOfWeekIndex;
278+ final startWeekday = (firstDayOfMonth.weekday - weekdayOffset + 7 ) % 7 ;
279+
280+ final weekdays = List .generate (7 , (i) {
281+ return loc.narrowWeekdays[(i + weekdayOffset) % 7 ];
282+ });
283+
284+ return Column (
285+ children: [
286+ Row (
287+ mainAxisAlignment: MainAxisAlignment .spaceAround,
288+ children: weekdays
289+ .map ((d) => SizedBox (
290+ width: 36 ,
291+ child: Text (
292+ d,
293+ textAlign: TextAlign .center,
294+ style: TextStyle (
295+ fontSize: 12 ,
296+ color: cs.onSurfaceVariant,
297+ ),
298+ ),
299+ ))
300+ .toList (),
301+ ),
302+ const SizedBox (height: 4 ),
303+ ...List .generate (_totalWeeks (firstDayOfMonth, lastDayOfMonth, startWeekday), (weekIndex) {
304+ return Row (
305+ mainAxisAlignment: MainAxisAlignment .spaceAround,
306+ children: List .generate (7 , (dayIndex) {
307+ final dayNumber = weekIndex * 7 + dayIndex - startWeekday + 1 ;
308+ if (dayNumber < 1 || dayNumber > lastDayOfMonth.day) {
309+ return const SizedBox (width: 36 , height: 36 );
310+ }
311+ final date = DateTime (currentMonth.year, currentMonth.month, dayNumber);
312+ final isPast = date.isBefore (today);
313+ final booked = _isBooked (date);
314+ final isToday = date == today;
315+
316+ return Container (
317+ width: 36 ,
318+ height: 36 ,
319+ alignment: Alignment .center,
320+ decoration: isToday
321+ ? BoxDecoration (
322+ color: cs.primary.withValues (alpha: 0.15 ),
323+ shape: BoxShape .circle,
324+ )
325+ : null ,
326+ child: Text (
327+ '$dayNumber ' ,
328+ style: TextStyle (
329+ fontSize: 14 ,
330+ color: booked || isPast
331+ ? cs.onSurface.withValues (alpha: 0.3 )
332+ : cs.onSurface,
333+ fontWeight: isToday ? FontWeight .bold : null ,
334+ ),
335+ ),
336+ );
337+ }),
338+ );
339+ }),
340+ ],
341+ );
342+ }
343+
344+ int _totalWeeks (DateTime first, DateTime last, int startWeekday) {
345+ final totalDays = last.day + startWeekday;
346+ return (totalDays / 7 ).ceil ();
347+ }
348+ }
349+
134350class _ItemRatingsSection extends StatelessWidget {
135351 final ItemDetail item;
136352
0 commit comments