diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart new file mode 100644 index 00000000..83f02df6 --- /dev/null +++ b/lib/pages/chat_list/chat_list_body.dart @@ -0,0 +1,219 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:animations/animations.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; +import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; +import 'package:fluffychat/pages/chat_list/stories_header.dart'; +import '../../utils/stream_extension.dart'; +import '../../widgets/matrix.dart'; + +class ChatListViewBody extends StatefulWidget { + final ChatListController controller; + + const ChatListViewBody(this.controller, {Key? key}) : super(key: key); + + @override + State createState() => _ChatListViewBodyState(); +} + +class _ChatListViewBodyState extends State { + // the matrix sync stream + late StreamSubscription _subscription; + late StreamSubscription _clientSubscription; + + // used to check the animation direction + String? _lastUserId; + String? _lastSpaceId; + + @override + void initState() { + _subscription = Matrix.of(context) + .client + .onSync + .stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)) + .listen((d) => setState(() {})); + _clientSubscription = + widget.controller.clientStream.listen((d) => setState(() {})); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final reversed = !_animationReversed(); + Widget child; + if (widget.controller.waitForFirstSync && + Matrix.of(context).client.prevBatch != null) { + final rooms = widget.controller.activeSpacesEntry.getRooms(context); + if (rooms.isEmpty) { + child = Column( + key: const ValueKey(null), + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + 'assets/private_chat_wallpaper.png', + width: 160, + height: 160, + ), + Center( + child: Text( + L10n.of(context)!.startYourFirstChat, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.grey, + fontSize: 16, + ), + ), + ), + ], + ); + } else { + final displayStoriesHeader = widget.controller.activeSpacesEntry + .shouldShowStoriesHeader(context); + child = ListView.builder( + key: ValueKey(Matrix.of(context).client.userID.toString() + + widget.controller.activeSpaceId.toString()), + controller: widget.controller.scrollController, + itemCount: rooms.length + (displayStoriesHeader ? 1 : 0), + itemBuilder: (BuildContext context, int i) { + if (displayStoriesHeader) { + if (i == 0) { + return const StoriesHeader(); + } + i--; + } + return ChatListItem( + rooms[i], + selected: widget.controller.selectedRoomIds.contains(rooms[i].id), + onTap: widget.controller.selectMode == SelectMode.select + ? () => widget.controller.toggleSelection(rooms[i].id) + : null, + onLongPress: () => widget.controller.toggleSelection(rooms[i].id), + activeChat: widget.controller.activeChat == rooms[i].id, + ); + }, + ); + } + } else { + const dummyChatCount = 5; + final titleColor = + Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100); + final subtitleColor = + Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50); + child = ListView.builder( + itemCount: dummyChatCount, + itemBuilder: (context, i) => Opacity( + opacity: (dummyChatCount - i) / dummyChatCount, + child: ListTile( + leading: CircleAvatar( + backgroundColor: titleColor, + child: CircularProgressIndicator( + strokeWidth: 1, + color: Theme.of(context).textTheme.bodyText1!.color, + ), + ), + title: Row( + children: [ + Expanded( + child: Container( + height: 14, + decoration: BoxDecoration( + color: titleColor, + borderRadius: BorderRadius.circular(3), + ), + ), + ), + const SizedBox(width: 36), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), + ), + ), + const SizedBox(width: 12), + Container( + height: 14, + width: 14, + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(14), + ), + ), + ], + ), + subtitle: Container( + decoration: BoxDecoration( + color: subtitleColor, + borderRadius: BorderRadius.circular(3), + ), + height: 12, + margin: const EdgeInsets.only(right: 22), + ), + ), + ), + ); + } + return PageTransitionSwitcher( + reverse: reversed, + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: + widget.controller.snappingSheetController.currentPosition == + kSpacesBottomBarHeight + ? SharedAxisTransitionType.horizontal + : SharedAxisTransitionType.vertical, + fillColor: Theme.of(context).scaffoldBackgroundColor, + child: child, + ); + }, + child: child, + ); + } + + @override + void dispose() { + _subscription.cancel(); + _clientSubscription.cancel(); + super.dispose(); + } + + bool _animationReversed() { + bool reversed; + // in case the matrix id changes, check the indexOf the matrix id + final newClient = Matrix.of(context).client; + if (_lastUserId != newClient.userID) { + reversed = Matrix.of(context) + .currentBundle! + .indexWhere((element) => element!.userID == _lastUserId) < + Matrix.of(context) + .currentBundle! + .indexWhere((element) => element!.userID == newClient.userID); + } + // otherwise, the space changed... + else { + reversed = widget.controller.spaces + .indexWhere((element) => element.id == _lastSpaceId) < + widget.controller.spaces.indexWhere( + (element) => element.id == widget.controller.activeSpaceId); + } + _lastUserId = newClient.userID; + _lastSpaceId = widget.controller.activeSpaceId; + return reversed; + } +} diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index e1bfb10b..9a1d91fa 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -1,25 +1,19 @@ -import 'dart:async'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:animations/animations.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; -import 'package:matrix/matrix.dart'; import 'package:snapping_sheet/snapping_sheet.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; -import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; -import 'package:fluffychat/pages/chat_list/stories_header.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; -import '../../utils/stream_extension.dart'; import '../../widgets/matrix.dart'; +import 'chat_list_body.dart'; class ChatListView extends StatelessWidget { final ChatListController controller; @@ -231,7 +225,7 @@ class ChatListView extends StatelessWidget { ), ), ), - Expanded(child: _ChatListViewBody(controller)), + Expanded(child: ChatListViewBody(controller)), ], ), initialSnappingPosition: showSpaces @@ -288,210 +282,6 @@ class ChatListView extends StatelessWidget { } } -class _ChatListViewBody extends StatefulWidget { - final ChatListController controller; - - const _ChatListViewBody(this.controller, {Key? key}) : super(key: key); - - @override - State<_ChatListViewBody> createState() => _ChatListViewBodyState(); -} - -class _ChatListViewBodyState extends State<_ChatListViewBody> { - // the matrix sync stream - late StreamSubscription _subscription; - late StreamSubscription _clientSubscription; - - // used to check the animation direction - String? _lastUserId; - String? _lastSpaceId; - - @override - void initState() { - _subscription = Matrix.of(context) - .client - .onSync - .stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)) - .listen((d) => setState(() {})); - _clientSubscription = - widget.controller.clientStream.listen((d) => setState(() {})); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final reversed = _animationReversed(); - Widget child; - if (widget.controller.waitForFirstSync && - Matrix.of(context).client.prevBatch != null) { - final rooms = widget.controller.activeSpacesEntry.getRooms(context); - if (rooms.isEmpty) { - child = Column( - key: const ValueKey(null), - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - 'assets/private_chat_wallpaper.png', - width: 160, - height: 160, - ), - Center( - child: Text( - L10n.of(context)!.startYourFirstChat, - textAlign: TextAlign.start, - style: const TextStyle( - color: Colors.grey, - fontSize: 16, - ), - ), - ), - ], - ); - } else { - final displayStoriesHeader = widget.controller.activeSpacesEntry - .shouldShowStoriesHeader(context); - child = ListView.builder( - key: ValueKey(Matrix.of(context).client.userID.toString() + - widget.controller.activeSpaceId.toString()), - controller: widget.controller.scrollController, - itemCount: rooms.length + (displayStoriesHeader ? 1 : 0), - itemBuilder: (BuildContext context, int i) { - if (displayStoriesHeader) { - if (i == 0) { - return const StoriesHeader(); - } - i--; - } - return ChatListItem( - rooms[i], - selected: widget.controller.selectedRoomIds.contains(rooms[i].id), - onTap: widget.controller.selectMode == SelectMode.select - ? () => widget.controller.toggleSelection(rooms[i].id) - : null, - onLongPress: () => widget.controller.toggleSelection(rooms[i].id), - activeChat: widget.controller.activeChat == rooms[i].id, - ); - }, - ); - } - } else { - const dummyChatCount = 5; - final titleColor = - Theme.of(context).textTheme.bodyText1!.color!.withAlpha(100); - final subtitleColor = - Theme.of(context).textTheme.bodyText1!.color!.withAlpha(50); - child = ListView.builder( - itemCount: dummyChatCount, - itemBuilder: (context, i) => Opacity( - opacity: (dummyChatCount - i) / dummyChatCount, - child: ListTile( - leading: CircleAvatar( - backgroundColor: titleColor, - child: CircularProgressIndicator( - strokeWidth: 1, - color: Theme.of(context).textTheme.bodyText1!.color, - ), - ), - title: Row( - children: [ - Expanded( - child: Container( - height: 14, - decoration: BoxDecoration( - color: titleColor, - borderRadius: BorderRadius.circular(3), - ), - ), - ), - const SizedBox(width: 36), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - const SizedBox(width: 12), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - ], - ), - subtitle: Container( - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(3), - ), - height: 12, - margin: const EdgeInsets.only(right: 22), - ), - ), - ), - ); - } - child = Material( - color: Theme.of(context).scaffoldBackgroundColor, - child: child, - ); - return PageTransitionSwitcher( - reverse: reversed, - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.horizontal, - child: child, - ); - }, - child: child, - ); - } - - @override - void dispose() { - _subscription.cancel(); - _clientSubscription.cancel(); - super.dispose(); - } - - bool _animationReversed() { - bool reversed; - // in case the matrix id changes, check the indexOf the matrix id - final newClient = Matrix.of(context).client; - if (_lastUserId != newClient.userID) { - reversed = Matrix.of(context) - .currentBundle! - .indexWhere((element) => element!.userID == _lastUserId) < - Matrix.of(context) - .currentBundle! - .indexWhere((element) => element!.userID == newClient.userID); - } - // otherwise, the space changed... - else { - reversed = widget.controller.spaces - .indexWhere((element) => element.id == _lastSpaceId) < - widget.controller.spaces.indexWhere( - (element) => element.id == widget.controller.activeSpaceId); - } - _lastUserId = newClient.userID; - _lastSpaceId = widget.controller.activeSpaceId; - return reversed; - } -} - enum ChatListPopupMenuItemActions { createGroup, createSpace,