fluffychat/lib/pages/chat_list/stories_header.dart

362 lines
13 KiB
Dart
Raw Normal View History

2021-12-24 13:18:09 +00:00
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
2022-04-08 08:12:58 +00:00
import 'package:collection/collection.dart';
2021-12-24 13:18:09 +00:00
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
2021-12-24 13:18:09 +00:00
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
2022-12-30 16:54:01 +00:00
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
2021-12-24 13:18:09 +00:00
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../config/themes.dart';
2021-12-24 13:18:09 +00:00
2021-12-25 17:47:36 +00:00
enum ContextualRoomAction {
mute,
unmute,
leave,
}
2021-12-24 13:18:09 +00:00
class StoriesHeader extends StatelessWidget {
2022-07-07 16:50:13 +00:00
final String filter;
const StoriesHeader({required this.filter, super.key});
2021-12-24 13:18:09 +00:00
void _addToStoryAction(BuildContext context) =>
2023-08-07 16:40:02 +00:00
context.go('/rooms/stories/create');
2021-12-24 13:18:09 +00:00
void _goToStoryAction(BuildContext context, String roomId) async {
final room = Matrix.of(context).client.getRoomById(roomId);
if (room == null) return;
if (room.membership != Membership.join) {
final result = await showFutureLoadingDialog(
context: context,
future: room.join,
);
if (result.error != null) return;
}
context.go('/rooms/stories/$roomId');
}
2021-12-24 13:18:09 +00:00
void _contextualActions(BuildContext context, Room room) async {
final action = await showModalActionSheet<ContextualRoomAction>(
2021-12-25 13:07:48 +00:00
cancelLabel: L10n.of(context)!.cancel,
2021-12-24 13:18:09 +00:00
context: context,
actions: [
if (room.pushRuleState != PushRuleState.notify)
SheetAction(
label: L10n.of(context)!.unmuteChat,
key: ContextualRoomAction.unmute,
icon: Icons.notifications_outlined,
)
else
SheetAction(
label: L10n.of(context)!.muteChat,
key: ContextualRoomAction.mute,
icon: Icons.notifications_off_outlined,
),
SheetAction(
label: L10n.of(context)!.unsubscribeStories,
key: ContextualRoomAction.leave,
icon: Icons.unsubscribe_outlined,
isDestructiveAction: true,
),
],
);
if (action == null) return;
switch (action) {
case ContextualRoomAction.mute:
await showFutureLoadingDialog(
context: context,
future: () => room.setPushRuleState(PushRuleState.dontNotify),
);
break;
case ContextualRoomAction.unmute:
await showFutureLoadingDialog(
context: context,
future: () => room.setPushRuleState(PushRuleState.notify),
);
break;
case ContextualRoomAction.leave:
await showFutureLoadingDialog(
context: context,
future: () => room.leave(),
);
break;
}
}
@override
Widget build(BuildContext context) {
final client = Matrix.of(context).client;
if (Matrix.of(context).shareContent != null) {
return ListTile(
leading: CircleAvatar(
radius: Avatar.defaultSize / 2,
backgroundColor: Theme.of(context).colorScheme.surface,
2023-01-26 08:47:30 +00:00
foregroundColor: Theme.of(context).textTheme.bodyLarge?.color,
child: const Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context)!.addToStory),
onTap: () => _addToStoryAction(context),
);
}
final ownStoryRoom = client.storiesRooms
.firstWhereOrNull((r) => r.creatorId == client.userID);
final stories = [
if (ownStoryRoom != null) ownStoryRoom,
...client.storiesRooms..remove(ownStoryRoom),
];
return SizedBox(
height: 104,
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12),
scrollDirection: Axis.horizontal,
itemCount: stories.length,
itemBuilder: (context, i) {
final room = stories[i];
final creator = room
.unsafeGetUserFromMemoryOrFallback(room.creatorId ?? 'Unknown');
final userId = room.creatorId;
final displayname = creator.calcDisplayname();
final avatarUrl = creator.avatarUrl;
if (!displayname.toLowerCase().contains(filter.toLowerCase())) {
return const SizedBox.shrink();
}
return _StoryButton(
profile: Profile(
displayName: displayname,
avatarUrl: avatarUrl,
userId: userId ?? 'Unknown',
),
lastMessage: room.hasPosts
? room.lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(
L10n.of(context)!,
),
)
: null,
heroTag: 'stories_${room.id}',
hasPosts: room.hasPosts || room == ownStoryRoom,
showEditFab: userId == client.userID,
unread: room.membership == Membership.invite ||
(room.hasNewMessages && room.hasPosts),
onPressed: () => _goToStoryAction(context, room.id),
onLongPressed: () => _contextualActions(context, room),
);
},
),
);
2021-12-24 13:18:09 +00:00
}
}
extension on Room {
2021-12-25 13:07:48 +00:00
bool get hasPosts {
2021-12-26 07:33:57 +00:00
if (membership == Membership.invite) return true;
2021-12-25 13:07:48 +00:00
final lastEvent = this.lastEvent;
if (lastEvent == null) return false;
if (lastEvent.type != EventTypes.Message) return false;
if (DateTime.now().difference(lastEvent.originServerTs).inHours >
ClientStoriesExtension.lifeTimeInHours) {
return false;
}
return true;
}
2021-12-24 13:18:09 +00:00
}
class _StoryButton extends StatefulWidget {
final Profile profile;
final bool showEditFab;
final bool unread;
2022-07-07 10:14:28 +00:00
final bool hasPosts;
2021-12-24 13:18:09 +00:00
final void Function() onPressed;
final void Function()? onLongPressed;
2022-07-08 08:41:36 +00:00
final String heroTag;
final String? lastMessage;
2021-12-24 13:18:09 +00:00
const _StoryButton({
required this.profile,
2021-12-24 13:18:09 +00:00
required this.onPressed,
2022-07-08 08:41:36 +00:00
required this.heroTag,
required this.lastMessage,
this.showEditFab = false,
2022-07-07 10:14:28 +00:00
this.hasPosts = true,
2021-12-24 13:18:09 +00:00
this.unread = false,
this.onLongPressed,
});
2021-12-24 13:18:09 +00:00
@override
State<_StoryButton> createState() => _StoryButtonState();
}
class _StoryButtonState extends State<_StoryButton> {
bool _hovered = false;
void _onHover(bool hover) {
if (hover == _hovered) return;
setState(() {
_hovered = hover;
});
}
2021-12-24 13:18:09 +00:00
@override
Widget build(BuildContext context) {
final lastMessage = widget.lastMessage;
final lastMessageBubbleElevation =
Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4;
final lastMessageBubbleShadowColor =
Theme.of(context).appBarTheme.shadowColor;
final lastMessageBubbleColor = Colors.white.withAlpha(245);
2023-08-16 05:30:48 +00:00
return SizedBox(
width: 82,
child: InkWell(
onHover: _onHover,
borderRadius: BorderRadius.circular(7),
onTap: widget.onPressed,
onLongPress: widget.onLongPressed,
child: Opacity(
opacity: widget.hasPosts ? 1 : 0.4,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Column(
children: [
const SizedBox(height: 8),
AnimatedScale(
scale: _hovered ? 1.15 : 1.0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: Material(
borderRadius: BorderRadius.circular(Avatar.defaultSize),
child: Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
gradient: widget.unread
? const LinearGradient(
colors: [
Colors.red,
Colors.purple,
Colors.orange,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: widget.unread
? null
: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(Avatar.defaultSize),
),
child: Stack(
children: [
Hero(
tag: widget.heroTag,
child: Avatar(
mxContent: widget.profile.avatarUrl,
name: widget.profile.displayName,
size: 72,
fontSize: 26,
),
),
2023-08-16 05:30:48 +00:00
if (widget.showEditFab)
Positioned(
right: 0,
bottom: 0,
child: SizedBox(
width: 24,
height: 24,
child: FloatingActionButton.small(
heroTag: null,
onPressed: () =>
context.go('/rooms/stories/create'),
child: const Icon(
Icons.add_outlined,
size: 16,
),
2022-07-07 10:14:28 +00:00
),
),
),
if (lastMessage != null) ...[
Positioned(
left: 0,
top: 0,
right: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Material(
elevation: lastMessageBubbleElevation,
shadowColor: lastMessageBubbleShadowColor,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 2,
),
color: lastMessageBubbleColor,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
lastMessage,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black,
fontSize: 11,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 26.0,
top: 4.0,
),
child: Center(
child: SizedBox(
width: 12,
height: 12,
child: Material(
elevation: lastMessageBubbleElevation,
shadowColor:
lastMessageBubbleShadowColor,
borderRadius:
BorderRadius.circular(99),
color: lastMessageBubbleColor,
),
),
),
),
],
),
),
2023-08-18 05:24:31 +00:00
],
2023-08-16 05:30:48 +00:00
],
),
2022-07-07 10:14:28 +00:00
),
2021-12-25 17:47:36 +00:00
),
2021-12-24 13:18:09 +00:00
),
2023-08-16 05:30:48 +00:00
Center(
child: Text(
widget.profile.displayName ?? '',
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: widget.unread ? FontWeight.bold : null,
),
2022-05-29 09:37:01 +00:00
),
2021-12-24 13:18:09 +00:00
),
2023-08-16 05:30:48 +00:00
],
),
2021-12-24 13:18:09 +00:00
),
),
),
);
}
}
2022-04-08 08:12:58 +00:00
extension on Room {
String? get creatorId => getState(EventTypes.RoomCreate)?.senderId;
}