diff --git a/lib/pages/add_story/add_story_view.dart b/lib/pages/add_story/add_story_view.dart index 9487b2df..5b2166c7 100644 --- a/lib/pages/add_story/add_story_view.dart +++ b/lib/pages/add_story/add_story_view.dart @@ -89,6 +89,7 @@ class AddStoryView extends StatelessWidget { controller: controller.controller, minLines: 1, maxLines: 20, + autofocus: true, textAlign: TextAlign.center, style: TextStyle( fontSize: 24, diff --git a/lib/pages/chat_list/stories_header.dart b/lib/pages/chat_list/stories_header.dart index 02791d2a..eaf873bf 100644 --- a/lib/pages/chat_list/stories_header.dart +++ b/lib/pages/chat_list/stories_header.dart @@ -26,6 +26,7 @@ class StoriesHeader extends StatelessWidget { void _contextualActions(BuildContext context, Room room) async { final action = await showModalActionSheet( + cancelLabel: L10n.of(context)!.cancel, context: context, actions: [ if (room.pushRuleState != PushRuleState.notify) @@ -105,19 +106,24 @@ class StoriesHeader extends StatelessWidget { child: const Icon(Icons.add), ), ...client.storiesRooms.map( - (room) => _StoryButton( - label: room.creatorDisplayname, - child: Avatar( - mxContent: room - .getState(EventTypes.RoomCreate)! - .sender - .avatarUrl, - name: room.creatorDisplayname, + (room) => Opacity( + opacity: room.hasPosts ? 1 : 0.5, + child: _StoryButton( + label: room.creatorDisplayname, + child: Avatar( + mxContent: room + .getState(EventTypes.RoomCreate)! + .sender + .avatarUrl, + name: room.creatorDisplayname, + ), + unread: room.notificationCount > 0 || + room.membership == Membership.invite, + onPressed: () => room.hasPosts + ? _goToStoryAction(context, room.id) + : _contextualActions(context, room), + onLongPressed: () => _contextualActions(context, room), ), - unread: room.notificationCount > 0 || - room.membership == Membership.invite, - onPressed: () => _goToStoryAction(context, room.id), - onLongPressed: () => _contextualActions(context, room), ), ), ], @@ -130,6 +136,17 @@ class StoriesHeader extends StatelessWidget { extension on Room { String get creatorDisplayname => getState(EventTypes.RoomCreate)!.sender.calcDisplayname(); + + bool get hasPosts { + 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; + } } class _StoryButton extends StatelessWidget { diff --git a/lib/pages/story/story_page.dart b/lib/pages/story/story_page.dart index 677e1752..9d50ef62 100644 --- a/lib/pages/story/story_page.dart +++ b/lib/pages/story/story_page.dart @@ -29,6 +29,12 @@ class StoryPageController extends State { Timer? _progressTimer; bool loadingMode = false; + final List events = []; + + Event? get currentEvent => index < events.length ? events[index] : null; + + final TextEditingController replyController = TextEditingController(); + static const Duration _step = Duration(milliseconds: 50); static const Duration maxProgress = Duration(seconds: 5); @@ -127,11 +133,11 @@ class StoryPageController extends State { .calcDisplayname() ?? 'Story not found'; - Future>? loadStory; + Future? loadStory; - Future> _loadStory() async { + Future _loadStory() async { final room = Matrix.of(context).client.getRoomById(roomId); - if (room == null) return []; + if (room == null) return; if (room.membership != Membership.join) { final joinedFuture = room.client.onSync.stream .where((u) => u.rooms?.join?.containsKey(room.id) ?? false) @@ -140,6 +146,7 @@ class StoryPageController extends State { await joinedFuture; } final timeline = await room.getTimeline(); + timeline.requestKeys(); var events = timeline.events.where((e) => e.type == EventTypes.Message).toList(); @@ -171,7 +178,24 @@ class StoryPageController extends State { .forEach((event) => downloadAndDecryptAttachment(event, event.messageType == MessageTypes.Video && PlatformInfos.isMobile)); - return events.reversed.toList(); + if (!events.last.receipts + .any((receipt) => receipt.user.id == room.client.userID)) { + for (var j = 0; j < events.length; j++) { + if (events[j] + .receipts + .any((receipt) => receipt.user.id == room.client.userID)) { + index = j; + room.setReadMarker( + events[index].eventId, + mRead: events[index].eventId, + ); + break; + } + } + } + this.events.clear(); + this.events.addAll(events.reversed.toList()); + return; } @override diff --git a/lib/pages/story/story_view.dart b/lib/pages/story/story_view.dart index d1cfb2a1..87eb259a 100644 --- a/lib/pages/story/story_view.dart +++ b/lib/pages/story/story_view.dart @@ -9,6 +9,7 @@ import 'package:matrix/matrix.dart'; import 'package:video_player/video_player.dart'; import 'package:fluffychat/pages/story/story_page.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/string_color.dart'; @@ -20,6 +21,7 @@ class StoryView extends StatelessWidget { @override Widget build(BuildContext context) { + final currentEvent = controller.currentEvent; return Scaffold( appBar: AppBar( titleSpacing: 0, @@ -29,7 +31,6 @@ class StoryView extends StatelessWidget { controller.title, style: const TextStyle( color: Colors.white, - fontSize: 20, shadows: [ Shadow( color: Colors.black, @@ -39,6 +40,21 @@ class StoryView extends StatelessWidget { ], ), ), + subtitle: currentEvent != null + ? Text( + currentEvent.originServerTs.localizedTime(context), + style: const TextStyle( + color: Colors.white70, + shadows: [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 5, + ), + ], + ), + ) + : null, leading: Avatar( mxContent: controller.avatar, name: controller.title, @@ -50,15 +66,15 @@ class StoryView extends StatelessWidget { backgroundColor: Colors.transparent, ), extendBodyBehindAppBar: true, - body: FutureBuilder>( + body: FutureBuilder( future: controller.loadStory, builder: (context, snapshot) { final error = snapshot.error; if (error != null) { return Center(child: Text(error.toLocalizedString(context))); } - final events = snapshot.data; - if (events == null) { + final events = controller.events; + if (snapshot.connectionState != ConnectionState.done) { return const Center( child: CircularProgressIndicator.adaptive( strokeWidth: 2,