Skip to content

Commit

Permalink
feat: home pending invites page (#713)
Browse files Browse the repository at this point in the history
# What ?
- Pending invites page 
https://trello.com/c/sKTDwRfN/672-home-pending-invites

# Screenshot 
<img width="384" alt="Screenshot 2024-09-04 at 15 31 40"
src="https://github.com/user-attachments/assets/8482368a-5bbd-48d8-befa-528069834d7c">
  • Loading branch information
mthinh authored Sep 6, 2024
1 parent da17d74 commit fa09f5e
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 7 deletions.
2 changes: 1 addition & 1 deletion assets/icons/ic_user_add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'package:app/core/presentation/pages/notification/widgets/notifications_list_view.dart';
import 'package:app/core/presentation/widgets/common/appbar/lemon_appbar_widget.dart';
import 'package:app/core/presentation/widgets/common/button/lemon_outline_button_widget.dart';
import 'package:app/core/presentation/widgets/theme_svg_icon_widget.dart';
import 'package:app/gen/assets.gen.dart';
import 'package:app/graphql/backend/schema.graphql.dart';
import 'package:app/i18n/i18n.g.dart';
import 'package:app/theme/sizing.dart';
import 'package:app/theme/spacing.dart';
import 'package:app/theme/typo.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

enum PendingInviteType {
all,
cohostRequest,
eventInvite,
friendRequest,
}

@RoutePage()
class HomePendingInvitesPage extends StatefulWidget {
const HomePendingInvitesPage({super.key});

@override
State<HomePendingInvitesPage> createState() => _HomePendingInvitesPageState();
}

class _HomePendingInvitesPageState extends State<HomePendingInvitesPage> {
PendingInviteType _selectedType = PendingInviteType.all;
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: LemonAppBar(
title: t.home.pendingInvites.title,
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: Sizing.medium,
child: ListView.separated(
padding: EdgeInsets.symmetric(
horizontal: Spacing.xSmall,
),
scrollDirection: Axis.horizontal,
itemCount: PendingInviteType.values.length,
separatorBuilder: (context, index) => SizedBox(
width: Spacing.extraSmall,
),
itemBuilder: (context, index) {
String label = '';
Widget? leading;
bool isSelected =
PendingInviteType.values[index] == _selectedType;
Color color = isSelected
? colorScheme.onPrimary
: colorScheme.onSecondary;
switch (PendingInviteType.values[index]) {
case PendingInviteType.all:
label = t.common.all;
break;
case PendingInviteType.cohostRequest:
label = t.home.pendingInvites.cohostRequest;
leading = ThemeSvgIcon(
color: color,
builder: (filter) => Assets.icons.icHostOutline.svg(
colorFilter: filter,
),
);
case PendingInviteType.eventInvite:
label = t.home.pendingInvites.eventInvite;
leading = ThemeSvgIcon(
color: color,
builder: (filter) => Assets.icons.icUserAdd.svg(
colorFilter: filter,
),
);
break;
case PendingInviteType.friendRequest:
label = t.home.pendingInvites.friendRequest;
leading = ThemeSvgIcon(
color: color,
builder: (filter) => Assets.icons.icAddReaction.svg(
colorFilter: filter,
),
);
break;
}
return LemonOutlineButton(
onTap: () => setState(
() => _selectedType = PendingInviteType.values[index],
),
label: label,
leading: leading,
radius: BorderRadius.circular(
LemonRadius.button,
),
textStyle: Typo.small.copyWith(
color: color,
),
);
},
),
),
SizedBox(
height: Sizing.xSmall,
),
if (_selectedType == PendingInviteType.all)
Expanded(
child: NotificationListView(
filter: Input$NotificationTypeFilterInput(
$in: [
Enum$NotificationType.event_cohost_request,
Enum$NotificationType.event_invite,
Enum$NotificationType.user_friendship_request,
],
),
),
),
if (_selectedType == PendingInviteType.cohostRequest)
Expanded(
child: NotificationListView(
filter: Input$NotificationTypeFilterInput(
eq: Enum$NotificationType.event_cohost_request,
),
),
),
if (_selectedType == PendingInviteType.eventInvite)
Expanded(
child: NotificationListView(
filter: Input$NotificationTypeFilterInput(
eq: Enum$NotificationType.event_invite,
),
),
),
if (_selectedType == PendingInviteType.friendRequest)
Expanded(
child: NotificationListView(
filter: Input$NotificationTypeFilterInput(
eq: Enum$NotificationType.user_friendship_request,
),
),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:app/core/application/notification/delete_notifications_bloc/delete_notifications_bloc.dart';
import 'package:app/core/presentation/pages/notification/widgets/notification_item_by_type/cohost_request_notification_item.dart';
import 'package:app/core/presentation/pages/notification/widgets/notification_item_by_type/default_notification_item.dart';
import 'package:app/core/presentation/pages/notification/widgets/notification_item_by_type/event_invited_notification_item.dart';
import 'package:app/core/presentation/pages/notification/widgets/notification_item_by_type/event_join_request_notification_item.dart';
Expand Down Expand Up @@ -139,6 +140,20 @@ class NotificationCardView extends StatelessWidget {
);
}

if (notification.type ==
Enum$NotificationType.event_cohost_request.name) {
return CohostRequestNotificationItem(
notification: notification,
onRemove: () {
onRemove?.call(
index,
notification,
false,
);
},
);
}

return DefaultNotificationItem(notification: notification);
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import 'package:app/core/presentation/pages/notification/widgets/notification_item_by_type/notification_item_base.dart';
import 'package:app/core/presentation/pages/notification/widgets/notification_thumbnail.dart';
import 'package:app/core/presentation/widgets/common/button/linear_gradient_button_widget.dart';
import 'package:app/core/presentation/widgets/future_loading_dialog.dart';
import 'package:app/core/presentation/widgets/image_placeholder_widget.dart';
import 'package:app/core/presentation/widgets/theme_svg_icon_widget.dart';
import 'package:app/core/utils/event_utils.dart';
import 'package:app/core/utils/gql/gql.dart';
import 'package:app/core/utils/image_utils.dart';
import 'package:app/gen/assets.gen.dart';
import 'package:app/graphql/backend/event/mutation/decide_event_cohost_request.graphql.dart';
import 'package:app/graphql/backend/schema.graphql.dart';
import 'package:app/i18n/i18n.g.dart';
import 'package:app/injection/register_module.dart';
import 'package:app/router/app_router.gr.dart';
import 'package:app/theme/sizing.dart';
import 'package:app/theme/spacing.dart';
import 'package:app/theme/typo.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:app/core/domain/notification/entities/notification.dart'
as notification_entities;

class CohostRequestNotificationItem extends StatelessWidget {
final notification_entities.Notification notification;
final Function()? onRemove;

const CohostRequestNotificationItem({
super.key,
required this.notification,
this.onRemove,
});

@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final t = Translations.of(context);
return NotificationBaseItem(
notification: notification,
icon: ThemeSvgIcon(
color: colorScheme.onSecondary,
builder: (colorFilter) => Assets.icons.icHostOutline.svg(
colorFilter: colorFilter,
width: Sizing.small,
height: Sizing.small,
),
),
avatar: NotificationThumbnail(
imageUrl: ImageUtils.generateUrl(
file: notification.fromExpanded?.newPhotosExpanded?.isNotEmpty == true
? notification.fromExpanded?.newPhotosExpanded!.first
: null,
),
onTap: () {
AutoRouter.of(context).push(
ProfileRoute(
userId: notification.from ?? '',
),
);
},
radius: BorderRadius.circular(Sizing.medium),
),
cover: NotificationThumbnail(
imageUrl: notification.refEventExpanded != null
? EventUtils.getEventThumbnailUrl(
event: notification.refEventExpanded!,
)
: '',
onTap: () {
AutoRouter.of(context).push(
EventDetailRoute(eventId: notification.refEventExpanded?.id ?? ''),
);
},
placeholder: ImagePlaceholder.eventCard(),
),
action: SizedBox(
height: Sizing.medium,
child: LinearGradientButton.primaryButton(
onTap: () async {
final response = await showFutureLoadingDialog(
context: context,
future: () async {
return await getIt<AppGQL>()
.client
.mutate$DecideEventCohostRequest(
Options$Mutation$DecideEventCohostRequest(
variables: Variables$Mutation$DecideEventCohostRequest(
input: Input$DecideEventCohostRequestInput(
event: notification.refEvent ?? '',
decision: true,
),
),
),
);
},
);
if (response.result?.parsedData?.decideEventCohostRequest == true) {
onRemove?.call();
}
},
textStyle: Typo.small.copyWith(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,
),
label: t.common.actions.accept,
),
),
extra: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (notification.fromExpanded?.username != null) ...[
Text(
'@${notification.fromExpanded?.username}',
style: Typo.medium.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
SizedBox(height: Spacing.superExtraSmall),
],
if (notification.fromExpanded?.description?.isNotEmpty == true ||
notification.fromExpanded?.jobTitle?.isNotEmpty == true)
Text(
notification.fromExpanded?.description ??
notification.fromExpanded?.jobTitle ??
'',
style: Typo.medium.copyWith(
color: colorScheme.onSecondary,
),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class NotificationListView extends StatefulWidget {

class _NotificationListViewState extends State<NotificationListView> {
List<String> removedNotificationIds = [];
bool hasNextPage = true;

void removeItem(
context, {
Expand Down Expand Up @@ -127,6 +128,13 @@ class _NotificationListViewState extends State<NotificationListView> {
...(fetchMoreResult?['getNotifications'] ?? [])
as List<dynamic>,
];
if (((fetchMoreResult?['getNotifications'] ?? [])
as List<dynamic>)
.isEmpty) {
setState(() {
hasNextPage = false;
});
}
fetchMoreResult?['getNotifications'] = finalList;
return fetchMoreResult;
},
Expand All @@ -143,12 +151,15 @@ class _NotificationListViewState extends State<NotificationListView> {
sliver: SliverList.builder(
itemBuilder: (ctx, index) {
if (index == notifications.length) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: Spacing.smMedium,
),
child: Loading.defaultLoading(context),
);
if (hasNextPage) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: Spacing.smMedium,
),
child: Loading.defaultLoading(context),
);
}
return const SizedBox.shrink();
}
if (removedNotificationIds
.contains(notifications[index].id)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mutation DecideEventCohostRequest($input: DecideEventCohostRequestInput!) {
decideEventCohostRequest(input: $input)
}
6 changes: 6 additions & 0 deletions lib/i18n/home/home_en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,11 @@
"postDesc": "Speak your mind",
"event": "Event",
"eventDesc": "Virutal, IRL, or hybrid"
},
"pendingInvites": {
"title": "Invites",
"cohostRequest": "Co-host request",
"eventInvite": "Event invite",
"friendRequest": "Friend request"
}
}
4 changes: 4 additions & 0 deletions lib/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ class AppRouter extends $AppRouter {
AutoRoute(page: AIRoute.page),
AutoRoute(page: MyEventsRoute.page),
AutoRoute(path: '/chat/detail/:id', page: ChatRoute.page),
AutoRoute(
path: '/pending-invites',
page: HomePendingInvitesRoute.page,
),
...chatRoutes,
eventBuyTicketsRoutes,
createEventRoutes,
Expand Down

0 comments on commit fa09f5e

Please sign in to comment.