mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-03 22:07:23 +01:00 
			
		
		
		
	Merge branch 'krille/betterstories' into 'main'
followup: Improve stories See merge request famedly/fluffychat!636
This commit is contained in:
		
						commit
						65698f1731
					
				@ -97,7 +97,7 @@ class AddStoryController extends State<AddStoryPage> {
 | 
			
		||||
 | 
			
		||||
  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<AddStoryPage> {
 | 
			
		||||
        builder: (context) => InviteStoryPage(storiesRoom: storiesRoom),
 | 
			
		||||
      );
 | 
			
		||||
      if (created != true) return;
 | 
			
		||||
      storiesRoom ??= await client.getStoriesRoom(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Post story
 | 
			
		||||
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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<ChatList> {
 | 
			
		||||
    if (room.isSpace && room.membership == Membership.join && !room.isUnread) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (room.getState(EventTypes.RoomCreate)?.content?.tryGet<String>('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) ??
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
                  ),
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ class StoryPageController extends State<StoryPage> {
 | 
			
		||||
 | 
			
		||||
  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<StoryPage> {
 | 
			
		||||
        })
 | 
			
		||||
      : 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<StoryPage> {
 | 
			
		||||
  Future<List<Event>> _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<StoryPage> {
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -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<List<Event>>(
 | 
			
		||||
@ -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,
 | 
			
		||||
                                  ),
 | 
			
		||||
                          ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
 | 
			
		||||
@ -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<String>('type') ==
 | 
			
		||||
          _storiesRoomType)
 | 
			
		||||
          storiesRoomType)
 | 
			
		||||
      .toList();
 | 
			
		||||
 | 
			
		||||
  Future<List<User>> getUndecidedContactsForStories(Room? storiesRoom) async {
 | 
			
		||||
@ -37,12 +37,12 @@ extension ClientStoriesExtension on Client {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<String> get storiesBlockList =>
 | 
			
		||||
      accountData[_storiesBlockListType]?.content.tryGetList<String>('users') ??
 | 
			
		||||
      accountData[storiesBlockListType]?.content.tryGetList<String>('users') ??
 | 
			
		||||
      [];
 | 
			
		||||
 | 
			
		||||
  Future<void> setStoriesBlockList(List<String> users) => setAccountData(
 | 
			
		||||
        userID!,
 | 
			
		||||
        _storiesBlockListType,
 | 
			
		||||
        storiesBlockListType,
 | 
			
		||||
        {'users': users},
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,7 @@ extension ClientStoriesExtension on Client {
 | 
			
		||||
  Future<Room?> getStoriesRoom(BuildContext context) async {
 | 
			
		||||
    final candidates = rooms.where((room) =>
 | 
			
		||||
        room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
 | 
			
		||||
            _storiesRoomType &&
 | 
			
		||||
            storiesRoomType &&
 | 
			
		||||
        room.ownPowerLevel >= 100);
 | 
			
		||||
    if (candidates.isEmpty) return null;
 | 
			
		||||
    if (candidates.length == 1) return candidates.single;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user