✨ Fortune graph
This commit is contained in:
193
lib/widgets/account/event_calendar.dart
Normal file
193
lib/widgets/account/event_calendar.dart
Normal file
@ -0,0 +1,193 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/activity.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
/// A reusable widget for displaying an event calendar with event details
|
||||
/// This can be used in various places throughout the app
|
||||
class EventCalendarWidget extends HookConsumerWidget {
|
||||
/// The list of calendar entries to display
|
||||
final AsyncValue<List<SnEventCalendarEntry>> events;
|
||||
|
||||
/// Initial date to focus on
|
||||
final DateTime? initialDate;
|
||||
|
||||
/// Whether to show the event details below the calendar
|
||||
final bool showEventDetails;
|
||||
|
||||
/// Whether to constrain the width of the calendar
|
||||
final bool constrainWidth;
|
||||
|
||||
/// Maximum width constraint when constrainWidth is true
|
||||
final double maxWidth;
|
||||
|
||||
/// Callback when a day is selected
|
||||
final void Function(DateTime)? onDaySelected;
|
||||
|
||||
/// Callback when the focused month changes
|
||||
final void Function(int year, int month)? onMonthChanged;
|
||||
|
||||
const EventCalendarWidget({
|
||||
super.key,
|
||||
required this.events,
|
||||
this.initialDate,
|
||||
this.showEventDetails = true,
|
||||
this.constrainWidth = false,
|
||||
this.maxWidth = 480,
|
||||
this.onDaySelected,
|
||||
this.onMonthChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedMonth = useState(initialDate?.month ?? DateTime.now().month);
|
||||
final selectedYear = useState(initialDate?.year ?? DateTime.now().year);
|
||||
final selectedDay = useState(initialDate ?? DateTime.now());
|
||||
|
||||
final content = Column(
|
||||
children: [
|
||||
TableCalendar(
|
||||
locale: EasyLocalization.of(context)!.locale.toString(),
|
||||
firstDay: DateTime.now().add(Duration(days: -3650)),
|
||||
lastDay: DateTime.now().add(Duration(days: 3650)),
|
||||
focusedDay: DateTime.utc(
|
||||
selectedYear.value,
|
||||
selectedMonth.value,
|
||||
selectedDay.value.day,
|
||||
),
|
||||
weekNumbersVisible: false,
|
||||
calendarFormat: CalendarFormat.month,
|
||||
selectedDayPredicate: (day) {
|
||||
return isSameDay(selectedDay.value, day);
|
||||
},
|
||||
onDaySelected: (value, _) {
|
||||
selectedDay.value = value;
|
||||
onDaySelected?.call(value);
|
||||
},
|
||||
onPageChanged: (focusedDay) {
|
||||
selectedMonth.value = focusedDay.month;
|
||||
selectedYear.value = focusedDay.year;
|
||||
onMonthChanged?.call(focusedDay.year, focusedDay.month);
|
||||
},
|
||||
eventLoader: (day) {
|
||||
return events.value
|
||||
?.where((e) => isSameDay(e.date, day))
|
||||
.expand((e) => [...e.statuses, e.checkInResult])
|
||||
.where((e) => e != null)
|
||||
.toList() ??
|
||||
[];
|
||||
},
|
||||
calendarBuilders: CalendarBuilders(
|
||||
dowBuilder: (context, day) {
|
||||
final text = DateFormat.EEEEE().format(day);
|
||||
return Center(child: Text(text));
|
||||
},
|
||||
markerBuilder: (context, day, events) {
|
||||
var checkInResult =
|
||||
events.whereType<SnCheckInResult>().firstOrNull;
|
||||
if (checkInResult != null) {
|
||||
return Positioned(
|
||||
top: 32,
|
||||
child: Text(
|
||||
'checkInResultT${checkInResult.level}'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color:
|
||||
isSameDay(selectedDay.value, day)
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer
|
||||
: isSameDay(DateTime.now(), day)
|
||||
? Theme.of(
|
||||
context,
|
||||
).colorScheme.onSecondaryContainer
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
if (showEventDetails) ...[
|
||||
const Divider(height: 1).padding(top: 8),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final event =
|
||||
events.value
|
||||
?.where((e) => isSameDay(e.date, selectedDay.value))
|
||||
.firstOrNull;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(DateFormat.EEEE().format(selectedDay.value))
|
||||
.fontSize(16)
|
||||
.bold()
|
||||
.textColor(
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
Text(DateFormat.yMd().format(selectedDay.value))
|
||||
.fontSize(12)
|
||||
.textColor(
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const Gap(16),
|
||||
if (event?.checkInResult != null)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'checkInResultLevel${event!.checkInResult!.level}',
|
||||
).tr().fontSize(16).bold(),
|
||||
for (final tip in event.checkInResult!.tips)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.circle,
|
||||
size: 12,
|
||||
fill: 1,
|
||||
).padding(top: 4, right: 4),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(tip.title).bold(),
|
||||
Text(tip.content),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(top: 8),
|
||||
],
|
||||
),
|
||||
if (event?.checkInResult == null &&
|
||||
(event?.statuses.isEmpty ?? true))
|
||||
Text('eventCalanderEmpty').tr(),
|
||||
],
|
||||
).padding(vertical: 24, horizontal: 24);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
if (constrainWidth) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: Card(margin: EdgeInsets.all(16), child: content),
|
||||
).center();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user