Skip to content

Commit

Permalink
Implement ErrorPage for Event Details, fixup RSVP provider into typed…
Browse files Browse the repository at this point in the history
… variant
  • Loading branch information
gnunicorn committed Aug 25, 2024
1 parent daf3417 commit 2b5bbd4
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 111 deletions.
6 changes: 3 additions & 3 deletions app/lib/features/calendar_sync/calendar_sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Future<void> _refreshCalendar(

Future<Event> _updateEventDetails(
CalendarEvent acterEvent,
String? rsvp,
RsvpStatusTag? rsvp,
Event localEvent,
) async {
localEvent.title = acterEvent.title();
Expand All @@ -203,8 +203,8 @@ Future<Event> _updateEventDetails(
UTC,
);
localEvent.status = switch (rsvp) {
'yes' => EventStatus.Confirmed,
'maybe' => EventStatus.Tentative,
RsvpStatusTag.Yes => EventStatus.Confirmed,
RsvpStatusTag.Maybe => EventStatus.Tentative,
_ => EventStatus.None
};
return localEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:acter/features/events/providers/event_providers.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

typedef EventAndRsvp = ({CalendarEvent event, String? rsvp});
typedef EventAndRsvp = ({CalendarEvent event, RsvpStatusTag? rsvp});

final eventsToSyncProvider = FutureProvider.autoDispose((ref) async {
// fetch all from all spaces
Expand All @@ -19,13 +19,12 @@ final eventsToSyncProvider = FutureProvider.autoDispose((ref) async {
for (final event in upcomingAndOngoing) {
final eventId = event.eventId().toString();
final myRsvpStatus = await ref.watch(myRsvpStatusProvider(eventId).future);
final rsvpStatus = myRsvpStatus.statusStr();
if (rsvpStatus != 'no') {
if (myRsvpStatus == RsvpStatusTag.No) {
// we sync all that aren't denied yet
final event = await ref.watch(
calendarEventProvider(eventId).future,
); // ensure we are listening to updates of the events themselves
toSync.add((event: event, rsvp: rsvpStatus));
toSync.add((event: event, rsvp: myRsvpStatus));
}
}
return toSync;
Expand Down
43 changes: 16 additions & 27 deletions app/lib/features/events/pages/event_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:acter/common/actions/redact_content.dart';
import 'package:acter/common/actions/report_content.dart';
import 'package:acter/common/providers/common_providers.dart';
import 'package:acter/common/providers/room_providers.dart';
import 'package:acter/common/toolkit/errors/error_page.dart';
import 'package:acter/common/utils/utils.dart';
import 'package:acter/common/widgets/edit_html_description_sheet.dart';
import 'package:acter/common/widgets/edit_title_sheet.dart';
Expand Down Expand Up @@ -72,9 +73,17 @@ class _EventDetailPageConsumerState extends ConsumerState<EventDetailPage> {
],
);
},
error: (e, s) {
_log.severe('Failed to load cal event', e, s);
return Text(L10n.of(context).errorLoadingEventDueTo(e));
error: (error, stack) {
_log.severe('Failed to load cal event', error, stack);
return ErrorPage(
background: const EventDetailsSkeleton(),
error: error,
stack: stack,
textBuilder: L10n.of(context).errorLoadingEventDueTo,
onRetryTap: () {
ref.invalidate(calendarEventProvider(widget.calendarId));
},
);
},
loading: () => const EventDetailsSkeleton(),
),
Expand Down Expand Up @@ -366,27 +375,7 @@ class _EventDetailPageConsumerState extends ConsumerState<EventDetailPage> {
}

Widget _buildEventRsvpActions(CalendarEvent calendarEvent) {
final myRsvpStatus = ref.watch(myRsvpStatusProvider(widget.calendarId));
Set<RsvpStatusTag?> rsvp = <RsvpStatusTag?>{null};
myRsvpStatus.maybeWhen(
data: (data) {
final status = data.statusStr();
if (status != null) {
switch (status) {
case 'yes':
rsvp = <RsvpStatusTag?>{RsvpStatusTag.Yes};
break;
case 'maybe':
rsvp = <RsvpStatusTag?>{RsvpStatusTag.Maybe};
break;
case 'no':
rsvp = <RsvpStatusTag?>{RsvpStatusTag.No};
break;
}
}
},
orElse: () => null,
);
final rsvp = ref.watch(myRsvpStatusProvider(widget.calendarId)).valueOrNull;

return Container(
color: Theme.of(context).colorScheme.surface,
Expand All @@ -400,7 +389,7 @@ class _EventDetailPageConsumerState extends ConsumerState<EventDetailPage> {
iconData: Icons.check,
actionName: L10n.of(context).going,
rsvpStatusColor: Theme.of(context).colorScheme.secondary,
isSelected: rsvp.single == RsvpStatusTag.Yes,
isSelected: rsvp == RsvpStatusTag.Yes,
),
_buildVerticalDivider(),
_buildEventRsvpActionItem(
Expand All @@ -410,7 +399,7 @@ class _EventDetailPageConsumerState extends ConsumerState<EventDetailPage> {
iconData: Icons.close,
actionName: L10n.of(context).notGoing,
rsvpStatusColor: Theme.of(context).colorScheme.error,
isSelected: rsvp.single == RsvpStatusTag.No,
isSelected: rsvp == RsvpStatusTag.No,
),
_buildVerticalDivider(),
_buildEventRsvpActionItem(
Expand All @@ -420,7 +409,7 @@ class _EventDetailPageConsumerState extends ConsumerState<EventDetailPage> {
iconData: Icons.question_mark,
actionName: L10n.of(context).maybe,
rsvpStatusColor: Colors.white,
isSelected: rsvp.single == RsvpStatusTag.Maybe,
isSelected: rsvp == RsvpStatusTag.Maybe,
),
],
),
Expand Down
8 changes: 4 additions & 4 deletions app/lib/features/events/providers/event_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final calendarEventProvider = AsyncNotifierProvider.autoDispose

//MY RSVP STATUS PROVIDER
final myRsvpStatusProvider = AsyncNotifierProvider.autoDispose
.family<AsyncRsvpStatusNotifier, ffi.OptionRsvpStatus, String>(
.family<AsyncRsvpStatusNotifier, ffi.RsvpStatusTag?, String>(
() => AsyncRsvpStatusNotifier(),
);

Expand Down Expand Up @@ -57,7 +57,7 @@ final myOngoingEventListProvider = FutureProvider.autoDispose
for (final event in allOngoingEventList) {
final myRsvpStatus = await ref
.watch(myRsvpStatusProvider(event.eventId().toString()).future);
if (myRsvpStatus.statusStr() == 'yes') {
if (myRsvpStatus == ffi.RsvpStatusTag.Yes) {
myOngoingEventList.add(event);
}
}
Expand All @@ -83,7 +83,7 @@ final myUpcomingEventListProvider = FutureProvider.autoDispose
for (final event in allUpcomingEventList) {
final myRsvpStatus = await ref
.watch(myRsvpStatusProvider(event.eventId().toString()).future);
if (myRsvpStatus.statusStr() == 'yes') {
if (myRsvpStatus == ffi.RsvpStatusTag.Yes) {
myUpcomingEventList.add(event);
}
}
Expand All @@ -109,7 +109,7 @@ final myPastEventListProvider = FutureProvider.autoDispose
for (final event in allPastEventList) {
final myRsvpStatus = await ref
.watch(myRsvpStatusProvider(event.eventId().toString()).future);
if (myRsvpStatus.statusStr() == 'yes') {
if (myRsvpStatus == ffi.RsvpStatusTag.Yes) {
myPastEventList.add(event);
}
}
Expand Down
13 changes: 9 additions & 4 deletions app/lib/features/events/providers/notifiers/rsvp_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' as ffi;
import 'package:riverpod/riverpod.dart';

class AsyncRsvpStatusNotifier
extends AutoDisposeFamilyAsyncNotifier<ffi.OptionRsvpStatus, String> {
extends AutoDisposeFamilyAsyncNotifier<ffi.RsvpStatusTag?, String> {
late Stream<bool> _listener;

Future<ffi.OptionRsvpStatus> _getMyResponse() async {
Future<ffi.RsvpStatusTag?> _getMyResponse() async {
final client = ref.read(alwaysClientProvider);
final calEvent = await client.waitForCalendarEvent(arg, null);
return await calEvent.respondedByMe();
return switch ((await calEvent.respondedByMe()).statusStr()) {
'yes' => ffi.RsvpStatusTag.Yes,
'no' => ffi.RsvpStatusTag.No,
'maybe' => ffi.RsvpStatusTag.Maybe,
_ => null,
};
}

@override
Future<ffi.OptionRsvpStatus> build(String arg) async {
Future<ffi.RsvpStatusTag?> build(String arg) async {
final client = ref.watch(alwaysClientProvider);
_listener =
client.subscribeStream('$arg::rsvp'); // keep it resident in memory
Expand Down
36 changes: 15 additions & 21 deletions app/lib/features/events/widgets/event_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:acter/features/events/actions/get_event_type.dart';
import 'package:acter/features/events/providers/event_providers.dart';
import 'package:acter/features/events/widgets/event_date_widget.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'
show CalendarEvent;
show CalendarEvent, RsvpStatusTag;
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand Down Expand Up @@ -110,8 +110,7 @@ class EventItem extends StatelessWidget {
final eventId = event.eventId().toString();
final rsvpLoader = ref.watch(myRsvpStatusProvider(eventId));
return rsvpLoader.when(
data: (rsvp) {
final status = rsvp.statusStr(); // kebab-case
data: (status) {
final widget = _getRsvpStatus(context, status); // kebab-case
return widget ?? const SizedBox.shrink();
},
Expand All @@ -132,24 +131,19 @@ class EventItem extends StatelessWidget {
);
}

Widget? _getRsvpStatus(BuildContext context, String? status) {
if (status != null) {
switch (status) {
case 'yes':
return Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
);
case 'no':
return Icon(
Icons.cancel,
color: Theme.of(context).colorScheme.error,
);
case 'maybe':
return const Icon(Icons.question_mark_rounded);
}
}
return null;
Widget? _getRsvpStatus(BuildContext context, RsvpStatusTag? status) {
return switch (status) {
RsvpStatusTag.Yes => Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
RsvpStatusTag.No => Icon(
Icons.cancel,
color: Theme.of(context).colorScheme.error,
),
RsvpStatusTag.Maybe => const Icon(Icons.question_mark_rounded),
null => null,
};
}

Widget _buildHappeningIndication(BuildContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,23 @@ import 'package:flutter/material.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

class EventDetailsSkeleton extends StatefulWidget {
class EventDetailsSkeleton extends StatelessWidget {
const EventDetailsSkeleton({super.key});

@override
State<EventDetailsSkeleton> createState() => _EventListSkeletonState();
}

class _EventListSkeletonState extends State<EventDetailsSkeleton> {
@override
Widget build(BuildContext context) {
return Skeletonizer(child: _buildSkeletonUI());
}

Widget _buildSkeletonUI() {
return Column(
children: [
_buildEventHeaderSkeletonUI(),
_buildEventBasicInfoSkeletonUI(),
_buildEventRsvpButtonsSkeletonUI(),
_buildEventAboutSkeletonUI(),
],
return SingleChildScrollView(
child: Column(
children: [
Skeletonizer(child: _buildEventHeaderSkeletonUI(context)),
Skeletonizer(child: _buildEventBasicInfoSkeletonUI(context)),
Skeletonizer(child: _buildEventRsvpButtonsSkeletonUI(context)),
Skeletonizer(child: _buildEventAboutSkeletonUI(context)),
],
),
);
}

Widget _buildEventHeaderSkeletonUI() {
Widget _buildEventHeaderSkeletonUI(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
Expand All @@ -42,7 +34,7 @@ class _EventListSkeletonState extends State<EventDetailsSkeleton> {
);
}

Widget _buildEventBasicInfoSkeletonUI() {
Widget _buildEventBasicInfoSkeletonUI(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
Expand All @@ -53,14 +45,12 @@ class _EventListSkeletonState extends State<EventDetailsSkeleton> {
color: Colors.white,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(L10n.of(context).eventTitleData),
Text(L10n.of(context).eventDescriptionsData),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(L10n.of(context).eventTitleData),
Text(L10n.of(context).eventDescriptionsData),
],
),
const SizedBox(width: 20),
Text(L10n.of(context).rsvp),
Expand All @@ -69,40 +59,35 @@ class _EventListSkeletonState extends State<EventDetailsSkeleton> {
);
}

Widget _buildEventRsvpButtonsSkeletonUI() {
Widget _buildEventRsvpButtonsSkeletonUI(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: Container(
height: 80,
width: 80,
color: Colors.white,
),
Container(
height: 80,
width: 80,
color: Colors.white,
),
const SizedBox(width: 20),
Expanded(
child: Container(
height: 80,
width: 80,
color: Colors.white,
),
Container(
height: 80,
width: 80,
color: Colors.white,
),
const SizedBox(width: 20),
Expanded(
child: Container(
height: 80,
width: 80,
color: Colors.white,
),
Container(
height: 80,
width: 80,
color: Colors.white,
),
],
),
);
}

Widget _buildEventAboutSkeletonUI() {
Widget _buildEventAboutSkeletonUI(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Container(
Expand Down
Loading

0 comments on commit 2b5bbd4

Please sign in to comment.