Initial commit
This commit is contained in:
412
lib/pages/chat_list/chat_list_item.dart
Normal file
412
lib/pages/chat_list/chat_list_item.dart
Normal file
@@ -0,0 +1,412 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/config/app_config.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/pages/chat_list/active_call_indicator.dart';
|
||||
import 'package:tatchat/pages/chat_list/unread_bubble.dart';
|
||||
import 'package:tatchat/utils/matrix_live_kit_calls/matrix_live_kit_call.dart';
|
||||
import 'package:tatchat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:tatchat/utils/room_status_extension.dart';
|
||||
import 'package:tatchat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:tatchat/widgets/future_loading_dialog.dart';
|
||||
import 'package:tatchat/widgets/hover_builder.dart';
|
||||
import 'package:tatchat/widgets/typing_animation.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../config/themes.dart';
|
||||
import '../../utils/date_time_extension.dart';
|
||||
import '../../widgets/avatar.dart';
|
||||
|
||||
class ChatListItem extends StatelessWidget {
|
||||
final Room room;
|
||||
final Room? space;
|
||||
final bool activeChat;
|
||||
final void Function(BuildContext context)? onLongPress;
|
||||
final void Function()? onForget;
|
||||
final void Function() onTap;
|
||||
final String? filter;
|
||||
|
||||
const ChatListItem(
|
||||
this.room, {
|
||||
this.activeChat = false,
|
||||
required this.onTap,
|
||||
this.onLongPress,
|
||||
this.onForget,
|
||||
this.filter,
|
||||
this.space,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final isMuted = room.pushRuleState != PushRuleState.notify;
|
||||
final typingText = room.getLocalizedTypingText(context);
|
||||
final lastEvent = room.lastEvent;
|
||||
final ownMessage = lastEvent?.senderId == room.client.userID;
|
||||
final directChatMatrixId = room.directChatMatrixID;
|
||||
final isDirectChat = directChatMatrixId != null;
|
||||
final hasNotifications = room.notificationCount > 0;
|
||||
final backgroundColor = activeChat
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: null;
|
||||
final displayname = room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
);
|
||||
final filter = this.filter;
|
||||
if (filter != null && !displayname.toLowerCase().contains(filter)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final needLastEventSender =
|
||||
lastEvent != null &&
|
||||
room.getState(EventTypes.RoomMember, lastEvent.senderId) == null;
|
||||
final space = this.space;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: backgroundColor,
|
||||
child: FutureBuilder(
|
||||
future: room.name.isEmpty ? room.loadHeroUsers() : null,
|
||||
builder: (context, _) => HoverBuilder(
|
||||
builder: (context, listTileHovered) => ListTile(
|
||||
minVerticalPadding: 16,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
onLongPress: () => onLongPress?.call(context),
|
||||
leading: HoverBuilder(
|
||||
builder: (context, hovered) => AnimatedScale(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
scale: hovered ? 1.1 : 1.0,
|
||||
child: SizedBox(
|
||||
width: Avatar.defaultSize,
|
||||
height: Avatar.defaultSize,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (space != null)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
child: Avatar(
|
||||
shapeBorder: RoundedSuperellipseBorder(
|
||||
side: BorderSide(
|
||||
width: 2,
|
||||
color:
|
||||
backgroundColor ??
|
||||
theme.colorScheme.surface,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.spaceBorderRadius * 0.75,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.spaceBorderRadius * 0.75,
|
||||
),
|
||||
mxContent: space.avatar,
|
||||
size: Avatar.defaultSize * 0.75,
|
||||
name: space.getLocalizedDisplayname(),
|
||||
onTap: () => onLongPress?.call(context),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Avatar(
|
||||
shapeBorder: space == null
|
||||
? room.isSpace
|
||||
? RoundedSuperellipseBorder(
|
||||
side: BorderSide(
|
||||
width: 1,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.spaceBorderRadius,
|
||||
),
|
||||
)
|
||||
: null
|
||||
: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
width: 2,
|
||||
color:
|
||||
backgroundColor ??
|
||||
theme.colorScheme.surface,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Avatar.defaultSize,
|
||||
),
|
||||
),
|
||||
borderRadius: room.isSpace
|
||||
? BorderRadius.circular(
|
||||
AppConfig.spaceBorderRadius,
|
||||
)
|
||||
: 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(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
displayname,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
),
|
||||
),
|
||||
if (isMuted)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 4.0),
|
||||
child: Icon(Icons.notifications_off_outlined, size: 16),
|
||||
),
|
||||
if (room.isLowPriority)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: hasNotifications ? 4.0 : 0.0,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.low_priority,
|
||||
size: 16,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
if (room.isFavourite)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: hasNotifications ? 4.0 : 0.0,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
size: 16,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
if (!room.isSpace && room.membership != Membership.invite)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text(
|
||||
room.latestEventReceivedTime.localizedTimeShort(
|
||||
context,
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: room.hasNewMessages
|
||||
? FontWeight.bold
|
||||
: null,
|
||||
color: room.highlightCount >= 1
|
||||
? theme.colorScheme.error
|
||||
: room.hasNewMessages
|
||||
? theme.colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
if (room.hasActiveMatrixRtcCall)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: ActiveCallIndicator(room: room),
|
||||
),
|
||||
if (typingText.isEmpty &&
|
||||
ownMessage &&
|
||||
room.lastEvent?.status.isSending == true) ...[
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
AnimatedSize(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: typingText.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: TypingAnimation(size: 4.0),
|
||||
)
|
||||
: room.lastEvent?.relationshipType ==
|
||||
RelationshipTypes.thread
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 4.0),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.message_outlined,
|
||||
size: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
L10n.of(context).thread,
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Expanded(
|
||||
child: room.isSpace && room.membership == Membership.join
|
||||
? Text(
|
||||
L10n.of(
|
||||
context,
|
||||
).countChats(room.spaceChildren.length),
|
||||
)
|
||||
: typingText.isNotEmpty
|
||||
? Text(
|
||||
typingText,
|
||||
style: TextStyle(color: theme.colorScheme.primary),
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
)
|
||||
: FutureBuilder(
|
||||
key: ValueKey(
|
||||
'${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
|
||||
),
|
||||
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
|
||||
? room
|
||||
.getState(
|
||||
EventTypes.RoomMember,
|
||||
room.client.userID!,
|
||||
)
|
||||
?.content
|
||||
.tryGet<String>('reason') ??
|
||||
(isDirectChat
|
||||
? L10n.of(context).newChatRequest
|
||||
: L10n.of(context).inviteGroupChat)
|
||||
: snapshot.data?.trim().replaceAll(
|
||||
'\n',
|
||||
' ',
|
||||
) ??
|
||||
L10n.of(context).noMessagesYet,
|
||||
softWrap: false,
|
||||
maxLines: room.notificationCount >= 1 ? 2 : 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
decoration: room.lastEvent?.redacted == true
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
UnreadBubble(room: room),
|
||||
],
|
||||
),
|
||||
onTap: onTap,
|
||||
trailing: onForget == null
|
||||
? room.membership == Membership.invite
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).declineInvitation,
|
||||
icon: const Icon(Icons.delete_forever_outlined),
|
||||
color: theme.colorScheme.error,
|
||||
onPressed: () async {
|
||||
final consent = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).declineInvitation,
|
||||
message: L10n.of(context).areYouSure,
|
||||
okLabel: L10n.of(context).yes,
|
||||
isDestructive: true,
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
if (!context.mounted) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: room.leave,
|
||||
);
|
||||
},
|
||||
)
|
||||
: null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
onPressed: onForget,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user