From b1a4d3a31428708f87ce8e23c77675d4c01856d2 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sat, 25 Dec 2021 08:56:35 +0100 Subject: [PATCH] followup: Improve stories --- lib/pages/add_story/add_story.dart | 3 +- lib/pages/add_story/add_story_view.dart | 26 +++- lib/pages/chat_list/chat_list.dart | 5 + lib/pages/chat_list/chat_list_view.dart | 7 +- lib/pages/chat_list/stories_header.dart | 3 +- lib/pages/story/story_page.dart | 21 ++- lib/pages/story/story_view.dart | 123 +++++++++++------- .../client_stories_extension.dart | 12 +- 8 files changed, 136 insertions(+), 64 deletions(-) diff --git a/lib/pages/add_story/add_story.dart b/lib/pages/add_story/add_story.dart index 44b8961e..b688ce2d 100644 --- a/lib/pages/add_story/add_story.dart +++ b/lib/pages/add_story/add_story.dart @@ -97,7 +97,7 @@ class AddStoryController extends State { void postStory() async { final client = Matrix.of(context).client; - final storiesRoom = await client.getStoriesRoom(context); + var storiesRoom = await client.getStoriesRoom(context); // Invite contacts if necessary final undecided = await showFutureLoadingDialog( @@ -113,6 +113,7 @@ class AddStoryController extends State { builder: (context) => InviteStoryPage(storiesRoom: storiesRoom), ); if (created != true) return; + storiesRoom ??= await client.getStoriesRoom(context); } // Post story diff --git a/lib/pages/add_story/add_story_view.dart b/lib/pages/add_story/add_story_view.dart index e83a5d4d..9487b2df 100644 --- a/lib/pages/add_story/add_story_view.dart +++ b/lib/pages/add_story/add_story_view.dart @@ -1,6 +1,7 @@ //@dart=2.12 import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:video_player/video_player.dart'; @@ -17,9 +18,23 @@ class AddStoryView extends StatelessWidget { final video = controller.videoPlayerController; return Scaffold( appBar: AppBar( - backgroundColor: - Theme.of(context).appBarTheme.backgroundColor?.withOpacity(0.5), - title: Text(L10n.of(context)!.addToStory), + systemOverlayStyle: SystemUiOverlayStyle.light, + backgroundColor: Colors.transparent, + elevation: 0, + iconTheme: const IconThemeData(color: Colors.white), + title: Text( + L10n.of(context)!.addToStory, + style: const TextStyle( + color: Colors.white, + shadows: [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 5, + ), + ], + ), + ), actions: controller.hasMedia ? null : [ @@ -62,12 +77,11 @@ class AddStoryView extends StatelessWidget { ? null : LinearGradient( colors: [ - controller.backgroundColor, controller.backgroundColorDark, controller.backgroundColor, ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), ), child: Center( diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index a27513b9..b94d5ef8 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -14,6 +14,7 @@ import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import '../../../utils/account_bundles.dart'; import '../../main.dart'; @@ -205,6 +206,10 @@ class ChatListController extends State { if (room.isSpace && room.membership == Membership.join && !room.isUnread) { return false; } + if (room.getState(EventTypes.RoomCreate)?.content?.tryGet('type') == + ClientStoriesExtension.storiesRoomType) { + return false; + } if (activeSpaceId != null) { final space = Matrix.of(context).client.getRoomById(activeSpaceId); if (space.spaceChildren?.any((child) => child.roomId == room.id) ?? diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index cd530105..b8509d59 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -204,7 +204,6 @@ class ChatListView extends StatelessWidget { ), ), ), - if (controller.waitForFirstSync) const StoriesHeader(), Expanded(child: _ChatListViewBody(controller)), ]), floatingActionButton: selectMode == SelectMode.normal @@ -307,8 +306,12 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> { key: ValueKey(Matrix.of(context).client.userID.toString() + widget.controller.activeSpaceId.toString()), controller: widget.controller.scrollController, - itemCount: rooms.length, + itemCount: rooms.length + 1, itemBuilder: (BuildContext context, int i) { + if (i == 0) { + return const StoriesHeader(); + } + i--; return ChatListItem( rooms[i], selected: widget.controller.selectedRoomIds.contains(rooms[i].id), diff --git a/lib/pages/chat_list/stories_header.dart b/lib/pages/chat_list/stories_header.dart index 60f0ad28..02791d2a 100644 --- a/lib/pages/chat_list/stories_header.dart +++ b/lib/pages/chat_list/stories_header.dart @@ -114,7 +114,8 @@ class StoriesHeader extends StatelessWidget { .avatarUrl, name: room.creatorDisplayname, ), - unread: room.notificationCount > 0, + unread: room.notificationCount > 0 || + room.membership == Membership.invite, onPressed: () => _goToStoryAction(context, room.id), onLongPressed: () => _contextualActions(context, room), ), diff --git a/lib/pages/story/story_page.dart b/lib/pages/story/story_page.dart index f85b7b50..677e1752 100644 --- a/lib/pages/story/story_page.dart +++ b/lib/pages/story/story_page.dart @@ -65,7 +65,7 @@ class StoryPageController extends State { void skip() { if (index + 1 >= max) { - VRouter.of(context).pop(); + VRouter.of(context).to('/rooms'); return; } setState(() { @@ -111,6 +111,13 @@ class StoryPageController extends State { }) : null; + Uri? get avatar => Matrix.of(context) + .client + .getRoomById(roomId) + ?.getState(EventTypes.RoomCreate) + ?.sender + .avatarUrl; + String get title => Matrix.of(context) .client @@ -125,6 +132,13 @@ class StoryPageController extends State { Future> _loadStory() async { final room = Matrix.of(context).client.getRoomById(roomId); if (room == null) return []; + if (room.membership != Membership.join) { + final joinedFuture = room.client.onSync.stream + .where((u) => u.rooms?.join?.containsKey(room.id) ?? false) + .first; + await room.join(); + await joinedFuture; + } final timeline = await room.getTimeline(); var events = timeline.events.where((e) => e.type == EventTypes.Message).toList(); @@ -149,12 +163,15 @@ class StoryPageController extends State { if (events.isNotEmpty) { _restartTimer(); } + + // Preload images and videos events .where((event) => {MessageTypes.Image, MessageTypes.Video} .contains(event.messageType)) .forEach((event) => downloadAndDecryptAttachment(event, event.messageType == MessageTypes.Video && PlatformInfos.isMobile)); - return events; + + return events.reversed.toList(); } @override diff --git a/lib/pages/story/story_view.dart b/lib/pages/story/story_view.dart index 48bd266c..d1cfb2a1 100644 --- a/lib/pages/story/story_view.dart +++ b/lib/pages/story/story_view.dart @@ -1,6 +1,7 @@ //@dart=2.12 import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -11,6 +12,7 @@ import 'package:fluffychat/pages/story/story_page.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/string_color.dart'; +import 'package:fluffychat/widgets/avatar.dart'; class StoryView extends StatelessWidget { final StoryPageController controller; @@ -20,9 +22,32 @@ class StoryView extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(controller.title), - backgroundColor: - Theme.of(context).appBarTheme.backgroundColor?.withOpacity(0.5), + titleSpacing: 0, + title: ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + controller.title, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + shadows: [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 5, + ), + ], + ), + ), + leading: Avatar( + mxContent: controller.avatar, + name: controller.title, + ), + ), + systemOverlayStyle: SystemUiOverlayStyle.light, + iconTheme: const IconThemeData(color: Colors.white), + elevation: 0, + backgroundColor: Colors.transparent, ), extendBodyBehindAppBar: true, body: FutureBuilder>( @@ -40,13 +65,25 @@ class StoryView extends StatelessWidget { )); } if (events.isEmpty) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - L10n.of(context)!.thisUserHasNotPostedAnythingYet, - textAlign: TextAlign.center, - )), + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Avatar( + mxContent: controller.avatar, + name: controller.title, + size: 128, + fontSize: 64, + ), + const SizedBox(height: 32), + Text( + L10n.of(context)!.thisUserHasNotPostedAnythingYet, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20), + ), + ], + ), ); } final event = events[controller.index]; @@ -115,12 +152,11 @@ class StoryView extends StatelessWidget { gradient: event.messageType == MessageTypes.Text ? LinearGradient( colors: [ - backgroundColor, backgroundColorDark, backgroundColor, ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ) : null, ), @@ -140,40 +176,35 @@ class StoryView extends StatelessWidget { ), ), Positioned( - bottom: 8, - left: 8, - right: 8, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - for (var i = 0; i < events.length; i++) - Container( - margin: const EdgeInsets.all(4), - width: 8, - height: 8, - decoration: BoxDecoration( - border: Border.all(color: Colors.black, width: 1), - color: i == controller.index - ? Colors.white - : Colors.grey.shade400, - borderRadius: BorderRadius.circular(8), - ), - ), - ], - ), - ), - Positioned( - top: 0, - left: 0, - right: 0, + top: 4, + left: 4, + right: 4, child: SafeArea( - child: LinearProgressIndicator( - color: Theme.of(context).primaryColor, - backgroundColor: Theme.of(context).colorScheme.surface, - value: controller.loadingMode - ? null - : controller.progress.inMilliseconds / - StoryPageController.maxProgress.inMilliseconds, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (var i = 0; i < events.length; i++) + Expanded( + child: i == controller.index + ? LinearProgressIndicator( + color: Colors.white, + minHeight: 2, + backgroundColor: Colors.grey.shade600, + value: controller.loadingMode + ? null + : controller.progress.inMilliseconds / + StoryPageController + .maxProgress.inMilliseconds, + ) + : Container( + margin: const EdgeInsets.all(4), + height: 2, + color: i < controller.index + ? Colors.white + : Colors.grey.shade600, + ), + ), + ], ), ), ), diff --git a/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart b/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart index a1d28c5d..64adc2d3 100644 --- a/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/client_stories_extension.dart @@ -6,8 +6,8 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:matrix/matrix.dart'; extension ClientStoriesExtension on Client { - static const String _storiesRoomType = 'msc3588.stories.stories-room'; - static const String _storiesBlockListType = 'msc3588.stories.block-list'; + static const String storiesRoomType = 'msc3588.stories.stories-room'; + static const String storiesBlockListType = 'msc3588.stories.block-list'; static const int lifeTimeInHours = 24; static const int maxPostsPerStory = 20; @@ -23,7 +23,7 @@ extension ClientStoriesExtension on Client { .getState(EventTypes.RoomCreate) ?.content .tryGet('type') == - _storiesRoomType) + storiesRoomType) .toList(); Future> getUndecidedContactsForStories(Room? storiesRoom) async { @@ -37,12 +37,12 @@ extension ClientStoriesExtension on Client { } List get storiesBlockList => - accountData[_storiesBlockListType]?.content.tryGetList('users') ?? + accountData[storiesBlockListType]?.content.tryGetList('users') ?? []; Future setStoriesBlockList(List users) => setAccountData( userID!, - _storiesBlockListType, + storiesBlockListType, {'users': users}, ); @@ -73,7 +73,7 @@ extension ClientStoriesExtension on Client { Future getStoriesRoom(BuildContext context) async { final candidates = rooms.where((room) => room.getState(EventTypes.RoomCreate)?.content.tryGet('type') == - _storiesRoomType && + storiesRoomType && room.ownPowerLevel >= 100); if (candidates.isEmpty) return null; if (candidates.length == 1) return candidates.single;