Skip to content

Commit

Permalink
Implemented activity invitation section with new UI
Browse files Browse the repository at this point in the history
  • Loading branch information
anisha-e10 committed Feb 21, 2025
1 parent e0c1c79 commit 288fb68
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 11 deletions.
4 changes: 2 additions & 2 deletions app/lib/common/themes/acter_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension ActerChatThemeExtension on ThemeData {
ActerChatTheme get chatTheme => const ActerChatTheme();

ElevatedButtonThemeData get dangerButtonTheme =>
dangerButtonThemeMaker(colorScheme);
dangerButtonThemeMaker(colorScheme, textTheme);

TextButtonThemeData get inlineTextButtonTheme =>
inlineTextButtonThemeMaker(colorScheme);
Expand Down Expand Up @@ -60,7 +60,7 @@ class ActerTheme {
dividerTheme: dividerTheme,
dialogTheme: dialogTheme,
bottomSheetTheme: bottomSheetTheme,
elevatedButtonTheme: elevatedButtonTheme(colorScheme),
elevatedButtonTheme: elevatedButtonTheme(colorScheme, textTheme),
outlinedButtonTheme: outlinedButtonTheme(colorScheme, textTheme),
textButtonTheme: textButtonTheme(colorScheme),
iconButtonTheme: iconButtonTheme,
Expand Down
21 changes: 16 additions & 5 deletions app/lib/common/themes/components/button_theme.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import 'package:acter/common/themes/colors/color_scheme.dart';
import 'package:flutter/material.dart';

ElevatedButtonThemeData elevatedButtonTheme(ColorScheme colors) =>
ElevatedButtonThemeData elevatedButtonTheme(ColorScheme colors, TextTheme textTheme) =>
ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
elevation: 0,
textStyle: textTheme.titleMedium?.copyWith(
color: colors.primary,
fontSize: 15,
),
backgroundColor: colors.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(10),
),
),
);

ElevatedButtonThemeData dangerButtonThemeMaker(ColorScheme colors) =>
ElevatedButtonThemeData dangerButtonThemeMaker(ColorScheme colors, TextTheme textTheme) =>
ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(18),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
elevation: 0,
textStyle: textTheme.titleMedium?.copyWith(
color: colors.primary,
fontSize: 15,
),
backgroundColor: colors.error,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(10),
),
),
);
Expand Down
6 changes: 4 additions & 2 deletions app/lib/features/activities/pages/activities_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import 'package:acter/common/providers/common_providers.dart';
import 'package:acter/common/utils/routes.dart';
import 'package:acter/common/widgets/empty_state_widget.dart';
import 'package:acter/features/activities/providers/activities_providers.dart';
import 'package:acter/features/activities/widgets/invitation_widget.dart';
import 'package:acter/features/backups/widgets/backup_state_widget.dart';
import 'package:acter/features/invitations/providers/invitations_providers.dart';
import 'package:acter/features/invitations/widgets/invitation_card.dart';
import 'package:acter/features/home/providers/client_providers.dart';
import 'package:acter/features/invitations/widgets/has_invites_tile.dart';
import 'package:acter/features/labs/model/labs_features.dart';
Expand Down Expand Up @@ -80,7 +80,9 @@ class ActivitiesPage extends ConsumerWidget {
showSectionBg: false,
isShowSeeAllButton: false,
),
InvitationCard(invitation: invitations.first),
InvitationWidget(
invitation: invitations.first,
),
];
}
return [
Expand Down
276 changes: 276 additions & 0 deletions app/lib/features/activities/widgets/invitation_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import 'dart:typed_data';
import 'package:acter/features/invitations/providers/invitations_providers.dart';
import 'package:acter/features/home/providers/client_providers.dart';
import 'package:acter/features/preview/actions/show_room_preview.dart';
import 'package:acter/router/utils.dart';
import 'package:acter_avatar/acter_avatar.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' show Invitation;
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';

final _log = Logger('a3::activities::invitation_widget');

class InvitationWidget extends ConsumerStatefulWidget {
final Invitation invitation;

const InvitationWidget({
super.key,
required this.invitation,
});

@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_InvitationWidgetState();
}

class _InvitationWidgetState extends ConsumerState<InvitationWidget> {
String? roomTitle;
late AvatarInfo avatarInfo;

@override
void initState() {
super.initState();
_initializeAvatarInfo();
_fetchDetails();
}

void _initializeAvatarInfo() {
setState(() {
avatarInfo = AvatarInfo(uniqueId: widget.invitation.roomIdStr());
});
}

void _fetchDetails() async {
final room = widget.invitation.room();
final title = await room.displayName();
setState(() {
roomTitle = title.text();
avatarInfo = AvatarInfo(
uniqueId: widget.invitation.roomIdStr(),
displayName: roomTitle,
);
});
final avatarData = (await room.avatar(null)).data();
if (avatarData != null) {
setState(() {
avatarInfo = AvatarInfo(
uniqueId: widget.invitation.roomIdStr(),
displayName: roomTitle,
avatar: MemoryImage(Uint8List.fromList(avatarData.asTypedList())),
);
});
}
}

@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildLeadingImageUI(),
const SizedBox(width: 8),
Expanded(
child: buildContentUI(context),
),
],
),
),
);
}

Widget buildLeadingImageUI() {
final isDM = widget.invitation.isDm();
final profile =
ref.watch(invitationUserProfileProvider(widget.invitation)).valueOrNull;
final roomId = widget.invitation.roomIdStr();

return ActerAvatar(
options: isDM
? AvatarOptions.DM(
AvatarInfo(
uniqueId: roomId,
displayName: profile?.displayName,
avatar: profile?.avatar,
),
size: 24,
)
: AvatarOptions(
avatarInfo,
size: 24,
),
);
}

Widget buildContentUI(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(context),
const SizedBox(height: 8),
buildInvitationType(context),
if (!widget.invitation.isDm()) ...[
const SizedBox(height: 4),
buildInviterChip(),
],
const SizedBox(height: 12),
buildActionButtons(context),
],
);
}

Widget buildTitle(BuildContext context) {
final isDM = widget.invitation.isDm();
final profile =
ref.watch(invitationUserProfileProvider(widget.invitation)).valueOrNull;
final roomId = widget.invitation.roomIdStr();
final senderId = widget.invitation.senderIdStr();

return GestureDetector(
onTap: () => showRoomPreview(context: context, roomIdOrAlias: roomId),
child: Text(
isDM ? (profile?.displayName ?? senderId) : (roomTitle ?? roomId),
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
}

Widget buildInvitationType(BuildContext context) {
final lang = L10n.of(context);
final isDM = widget.invitation.isDm();
final isSpace = widget.invitation.room().isSpace();

return Text(
isDM
? lang.invitationToDM
: (isSpace ? lang.invitationToSpace : lang.invitationToChat),
style: Theme.of(context).textTheme.bodyMedium,
);
}

Widget buildInviterChip() {
final profile =
ref.watch(invitationUserProfileProvider(widget.invitation)).valueOrNull;
final senderId = widget.invitation.senderIdStr();

return Chip(
visualDensity: VisualDensity.compact,
avatar: ActerAvatar(
options: AvatarOptions.DM(
AvatarInfo(
uniqueId: senderId,
displayName: profile?.displayName,
avatar: profile?.avatar,
),
size: 24,
),
),
label: Text(profile?.displayName ?? senderId),
);
}

Widget buildActionButtons(BuildContext context) {
final lang = L10n.of(context);

return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () => _onTapDeclineInvite(context),
child: Text(lang.decline),
),
const SizedBox(width: 8),
OutlinedButton(
onPressed: () => _onTapAcceptInvite(context),
child: Text(lang.accept),
),
],
);
}

void _onTapAcceptInvite(BuildContext context) async {
final lang = L10n.of(context);
EasyLoading.show(status: lang.joining);
final client = await ref.read(alwaysClientProvider.future);
final roomId = widget.invitation.roomIdStr();
final isSpace = widget.invitation.room().isSpace();
try {
await widget.invitation.accept();
} catch (e, s) {
_log.severe('Failure accepting invite', e, s);
if (!context.mounted) {
EasyLoading.dismiss();
return;
}
EasyLoading.showError(
lang.failedToAcceptInvite(e),
duration: const Duration(seconds: 3),
);
return;
}

try {
// timeout to wait for 10seconds to ensure the room is ready
await client.waitForRoom(roomId, 10);
} catch (e) {
if (!context.mounted) {
EasyLoading.dismiss();
return;
}
EasyLoading.showToast(lang.joinedDelayed);
// do not forward in this case
return;
}
if (!context.mounted) {
EasyLoading.dismiss();
return;
}
EasyLoading.showToast(lang.joined);
if (isSpace) {
goToSpace(context, roomId);
} else {
goToChat(context, roomId);
}
}

void _onTapDeclineInvite(BuildContext context) async {
final lang = L10n.of(context);
EasyLoading.show(status: lang.rejecting);
try {
bool res = await widget.invitation.reject();
ref.invalidate(invitationListProvider);
if (!context.mounted) {
EasyLoading.dismiss();
return;
}
if (res) {
EasyLoading.showToast(lang.rejected);
} else {
_log.severe('Failed to reject invitation');
EasyLoading.showError(
lang.failedToReject,
duration: const Duration(seconds: 3),
);
}
} catch (e, s) {
_log.severe('Failure reject invite', e, s);
if (!context.mounted) {
EasyLoading.dismiss();
return;
}
EasyLoading.showError(
lang.failedToRejectInvite(e),
duration: const Duration(seconds: 3),
);
}
}
}
4 changes: 2 additions & 2 deletions app/lib/features/invitations/pages/invites_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:acter/common/widgets/empty_state_widget.dart';
import 'package:acter/features/activities/widgets/invitation_widget.dart';
import 'package:acter/features/invitations/providers/invitations_providers.dart';
import 'package:acter/features/invitations/widgets/invitation_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
Expand Down Expand Up @@ -29,7 +29,7 @@ class InvitesPage extends ConsumerWidget {
);
}
return ListView.builder(
itemBuilder: (context, index) => InvitationCard(
itemBuilder: (context, index) => InvitationWidget(
invitation: invitations[index],
),
itemCount: invitations.length,
Expand Down

0 comments on commit 288fb68

Please sign in to comment.