feat: Swipe to archive rooms

This commit is contained in:
Krille 2024-10-22 09:22:48 +02:00
commit 3f9c7f3e91
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652

View file

@ -35,29 +35,31 @@ class ChatListItem extends StatelessWidget {
super.key, super.key,
}); });
Future<void> archiveAction(BuildContext context) async { Future<bool> archiveAction(BuildContext context, {bool? confirmed}) async {
{ {
if ([Membership.leave, Membership.ban].contains(room.membership)) { if ([Membership.leave, Membership.ban].contains(room.membership)) {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.forget(), future: () => room.forget(),
); );
return; return true;
} }
final confirmed = await showOkCancelAlertDialog( confirmed ??= (await showOkCancelAlertDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
title: L10n.of(context).areYouSure, title: L10n.of(context).areYouSure,
okLabel: L10n.of(context).yes, okLabel: L10n.of(context).leave,
cancelLabel: L10n.of(context).no, cancelLabel: L10n.of(context).cancel,
message: L10n.of(context).archiveRoomDescription, message: L10n.of(context).archiveRoomDescription,
); isDestructiveAction: true,
if (confirmed == OkCancelResult.cancel) return; )) ==
OkCancelResult.ok;
if (!confirmed) return false;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () => room.leave(), future: () => room.leave(),
); );
return; return true;
} }
} }
@ -93,294 +95,323 @@ class ChatListItem extends StatelessWidget {
: room.getState(EventTypes.RoomMember, lastEvent.senderId) == null; : room.getState(EventTypes.RoomMember, lastEvent.senderId) == null;
final space = this.space; final space = this.space;
return Padding( return Dismissible(
padding: const EdgeInsets.symmetric( key: ValueKey(room.id),
horizontal: 8, confirmDismiss: (_) async =>
vertical: 1, (await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
title: L10n.of(context).areYouSure,
okLabel: L10n.of(context).leave,
cancelLabel: L10n.of(context).cancel,
message: L10n.of(context).archiveRoomDescription,
isDestructiveAction: true,
)) ==
OkCancelResult.ok,
onDismissed: (_) => archiveAction(context, confirmed: true),
background: Material(
color: theme.colorScheme.errorContainer,
child: Icon(
Icons.archive_outlined,
color: theme.colorScheme.onErrorContainer,
),
), ),
child: Material( child: Padding(
borderRadius: BorderRadius.circular(AppConfig.borderRadius), padding: const EdgeInsets.symmetric(
clipBehavior: Clip.hardEdge, horizontal: 8,
color: backgroundColor, vertical: 1,
child: FutureBuilder( ),
future: room.loadHeroUsers(), child: Material(
builder: (context, snapshot) => HoverBuilder( borderRadius: BorderRadius.circular(AppConfig.borderRadius),
builder: (context, listTileHovered) => ListTile( clipBehavior: Clip.hardEdge,
visualDensity: const VisualDensity(vertical: -0.5), color: backgroundColor,
contentPadding: const EdgeInsets.symmetric(horizontal: 8), child: FutureBuilder(
onLongPress: () => onLongPress?.call(context), future: room.loadHeroUsers(),
leading: HoverBuilder( builder: (context, snapshot) => HoverBuilder(
builder: (context, hovered) => AnimatedScale( builder: (context, listTileHovered) => ListTile(
duration: FluffyThemes.animationDuration, visualDensity: const VisualDensity(vertical: -0.5),
curve: FluffyThemes.animationCurve, contentPadding: const EdgeInsets.symmetric(horizontal: 8),
scale: hovered ? 1.1 : 1.0, onLongPress: () => onLongPress?.call(context),
child: SizedBox( leading: HoverBuilder(
width: Avatar.defaultSize, builder: (context, hovered) => AnimatedScale(
height: Avatar.defaultSize, duration: FluffyThemes.animationDuration,
child: Stack( curve: FluffyThemes.animationCurve,
children: [ scale: hovered ? 1.1 : 1.0,
if (space != null) child: SizedBox(
width: Avatar.defaultSize,
height: Avatar.defaultSize,
child: Stack(
children: [
if (space != null)
Positioned(
top: 0,
left: 0,
child: Avatar(
border: BorderSide(
width: 2,
color: backgroundColor ??
theme.colorScheme.surface,
),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 4,
),
mxContent: space.avatar,
size: Avatar.defaultSize * 0.75,
name: space.getLocalizedDisplayname(),
onTap: () => onLongPress?.call(context),
),
),
Positioned( Positioned(
top: 0, bottom: 0,
left: 0, right: 0,
child: Avatar( child: Avatar(
border: BorderSide( border: space == null
width: 2, ? null
color: backgroundColor ?? : BorderSide(
theme.colorScheme.surface, width: 2,
), color: backgroundColor ??
borderRadius: BorderRadius.circular( theme.colorScheme.surface,
AppConfig.borderRadius / 4, ),
), borderRadius: room.isSpace
mxContent: space.avatar, ? BorderRadius.circular(
size: Avatar.defaultSize * 0.75, AppConfig.borderRadius / 4,
name: space.getLocalizedDisplayname(), )
: null,
mxContent: room.avatar,
size: space != null
? Avatar.defaultSize * 0.75
: Avatar.defaultSize,
name: displayname,
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: () => onLongPress?.call(context), onTap: () => onLongPress?.call(context),
), ),
), ),
Positioned( Positioned(
bottom: 0, top: 0,
right: 0, right: 0,
child: Avatar( child: GestureDetector(
border: space == null onTap: () => onLongPress?.call(context),
? null child: AnimatedScale(
: BorderSide( duration: FluffyThemes.animationDuration,
width: 2, curve: FluffyThemes.animationCurve,
color: backgroundColor ?? scale: listTileHovered ? 1.0 : 0.0,
theme.colorScheme.surface, child: Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
child: const Icon(
Icons.arrow_drop_down_circle_outlined,
size: 18,
), ),
borderRadius: room.isSpace
? BorderRadius.circular(
AppConfig.borderRadius / 4,
)
: null,
mxContent: room.avatar,
size: space != null
? Avatar.defaultSize * 0.75
: Avatar.defaultSize,
name: displayname,
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: () => onLongPress?.call(context),
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: () => onLongPress?.call(context),
child: AnimatedScale(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
scale: listTileHovered ? 1.0 : 0.0,
child: Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
child: const Icon(
Icons.arrow_drop_down_circle_outlined,
size: 18,
), ),
), ),
), ),
), ),
), ],
], ),
), ),
), ),
), ),
), title: Row(
title: Row( children: <Widget>[
children: <Widget>[ Expanded(
Expanded(
child: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: unread || room.hasNewMessages
? const TextStyle(fontWeight: FontWeight.bold)
: null,
),
),
if (isMuted)
const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off_outlined,
size: 16,
),
),
if (room.isFavourite || room.membership == Membership.invite)
Padding(
padding: EdgeInsets.only(
right: hasNotifications ? 4.0 : 0.0,
),
child: Icon(
Icons.push_pin,
size: 16,
color: theme.colorScheme.primary,
),
),
if (!room.isSpace &&
lastEvent != null &&
room.membership != Membership.invite)
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text( child: Text(
lastEvent.originServerTs.localizedTimeShort(context), displayname,
style: TextStyle( maxLines: 1,
fontSize: 13, overflow: TextOverflow.ellipsis,
color: unread softWrap: false,
? theme.colorScheme.secondary style: unread || room.hasNewMessages
: theme.textTheme.bodyMedium!.color, ? const TextStyle(fontWeight: FontWeight.bold)
: null,
),
),
if (isMuted)
const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off_outlined,
size: 16,
), ),
), ),
), if (room.isFavourite ||
if (room.isSpace) room.membership == Membership.invite)
const Icon( Padding(
Icons.arrow_circle_right_outlined, padding: EdgeInsets.only(
size: 18, right: hasNotifications ? 4.0 : 0.0,
), ),
], child: Icon(
), Icons.push_pin,
subtitle: Row( size: 16,
mainAxisAlignment: MainAxisAlignment.center, color: theme.colorScheme.primary,
children: <Widget>[ ),
if (typingText.isEmpty && ),
ownMessage && if (!room.isSpace &&
room.lastEvent!.status.isSending) ...[ lastEvent != null &&
const SizedBox( room.membership != Membership.invite)
width: 16, Padding(
height: 16, padding: const EdgeInsets.only(left: 4.0),
child: CircularProgressIndicator.adaptive(strokeWidth: 2), child: Text(
), lastEvent.originServerTs.localizedTimeShort(context),
const SizedBox(width: 4), style: TextStyle(
fontSize: 13,
color: unread
? theme.colorScheme.secondary
: theme.textTheme.bodyMedium!.color,
),
),
),
if (room.isSpace)
const Icon(
Icons.arrow_circle_right_outlined,
size: 18,
),
], ],
AnimatedContainer( ),
width: typingText.isEmpty ? 0 : 18, subtitle: Row(
clipBehavior: Clip.hardEdge, mainAxisAlignment: MainAxisAlignment.center,
decoration: const BoxDecoration(), children: <Widget>[
duration: FluffyThemes.animationDuration, if (typingText.isEmpty &&
curve: FluffyThemes.animationCurve, ownMessage &&
padding: const EdgeInsets.only(right: 4), room.lastEvent!.status.isSending) ...[
child: Icon( const SizedBox(
Icons.edit_outlined, width: 16,
color: theme.colorScheme.secondary, height: 16,
size: 14, child:
CircularProgressIndicator.adaptive(strokeWidth: 2),
),
const SizedBox(width: 4),
],
AnimatedContainer(
width: typingText.isEmpty ? 0 : 18,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
padding: const EdgeInsets.only(right: 4),
child: Icon(
Icons.edit_outlined,
color: theme.colorScheme.secondary,
size: 14,
),
), ),
), Expanded(
Expanded( child: room.isSpace && room.membership == Membership.join
child: room.isSpace && room.membership == Membership.join
? Text(
L10n.of(context).countChatsAndCountParticipants(
room.spaceChildren.length.toString(),
(room.summary.mJoinedMemberCount ?? 1).toString(),
),
)
: typingText.isNotEmpty
? Text(
typingText,
style: TextStyle(
color: theme.colorScheme.primary,
),
maxLines: 1,
softWrap: false,
)
: FutureBuilder(
key: ValueKey(
'${lastEvent?.eventId}_${lastEvent?.type}',
),
future: needLastEventSender
? lastEvent.calcLocalizedBody(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId),
)
: null,
initialData:
lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId),
),
builder: (context, snapshot) => Text(
room.membership == Membership.invite
? isDirectChat
? L10n.of(context).invitePrivateChat
: L10n.of(context).inviteGroupChat
: snapshot.data ??
L10n.of(context).emptyChat,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: unread || room.hasNewMessages
? FontWeight.bold
: null,
color: theme.colorScheme.onSurfaceVariant,
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
),
),
),
),
const SizedBox(width: 8),
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
padding: const EdgeInsets.symmetric(horizontal: 7),
height: unreadBubbleSize,
width: !hasNotifications && !unread && !room.hasNewMessages
? 0
: (unreadBubbleSize - 9) *
room.notificationCount.toString().length +
9,
decoration: BoxDecoration(
color: room.highlightCount > 0 ||
room.membership == Membership.invite
? Colors.red
: hasNotifications || room.markedUnread
? theme.colorScheme.primary
: theme.colorScheme.primaryContainer,
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
child: Center(
child: hasNotifications
? Text( ? Text(
room.notificationCount.toString(), L10n.of(context).countChatsAndCountParticipants(
style: TextStyle( room.spaceChildren.length.toString(),
color: room.highlightCount > 0 (room.summary.mJoinedMemberCount ?? 1)
? Colors.white .toString(),
: hasNotifications
? theme.colorScheme.onPrimary
: theme.colorScheme.onPrimaryContainer,
fontSize: 13,
), ),
) )
: const SizedBox.shrink(), : typingText.isNotEmpty
? Text(
typingText,
style: TextStyle(
color: theme.colorScheme.primary,
),
maxLines: 1,
softWrap: false,
)
: FutureBuilder(
key: ValueKey(
'${lastEvent?.eventId}_${lastEvent?.type}',
),
future: needLastEventSender
? lastEvent.calcLocalizedBody(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix:
(!isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId),
)
: null,
initialData:
lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId),
),
builder: (context, snapshot) => Text(
room.membership == Membership.invite
? isDirectChat
? L10n.of(context).invitePrivateChat
: L10n.of(context).inviteGroupChat
: snapshot.data ??
L10n.of(context).emptyChat,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: unread || room.hasNewMessages
? FontWeight.bold
: null,
color: theme.colorScheme.onSurfaceVariant,
decoration:
room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
),
),
),
), ),
), const SizedBox(width: 8),
], AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
padding: const EdgeInsets.symmetric(horizontal: 7),
height: unreadBubbleSize,
width:
!hasNotifications && !unread && !room.hasNewMessages
? 0
: (unreadBubbleSize - 9) *
room.notificationCount.toString().length +
9,
decoration: BoxDecoration(
color: room.highlightCount > 0 ||
room.membership == Membership.invite
? Colors.red
: hasNotifications || room.markedUnread
? theme.colorScheme.primary
: theme.colorScheme.primaryContainer,
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
child: Center(
child: hasNotifications
? Text(
room.notificationCount.toString(),
style: TextStyle(
color: room.highlightCount > 0
? Colors.white
: hasNotifications
? theme.colorScheme.onPrimary
: theme
.colorScheme.onPrimaryContainer,
fontSize: 13,
),
)
: const SizedBox.shrink(),
),
),
],
),
onTap: onTap,
trailing: onForget == null
? null
: IconButton(
icon: const Icon(Icons.delete_outlined),
onPressed: onForget,
),
), ),
onTap: onTap,
trailing: onForget == null
? null
: IconButton(
icon: const Icon(Icons.delete_outlined),
onPressed: onForget,
),
), ),
), ),
), ),