diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 7253bd83..a27513b9 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -47,6 +47,7 @@ class ChatListController extends State { StreamSubscription _intentUriStreamSubscription; String _activeSpaceId; + String get activeSpaceId => Matrix.of(context).client.getRoomById(_activeSpaceId) == null ? null @@ -54,6 +55,10 @@ class ChatListController extends State { final ScrollController scrollController = ScrollController(); bool scrolledToTop = true; + final StreamController _clientStream = StreamController.broadcast(); + + Stream get clientStream => _clientStream.stream; + void _onScroll() { final newScrolledToTop = scrollController.position.pixels <= 0; if (newScrolledToTop != scrolledToTop) { @@ -478,6 +483,7 @@ class ChatListController extends State { selectedRoomIds.clear(); Matrix.of(context).setActiveClient(client); }); + _clientStream.add(client); } void setActiveBundle(String bundle) { diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 29df1bf2..b27c2ec1 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:flutter/cupertino.dart'; 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:vrouter/vrouter.dart'; @@ -233,77 +234,147 @@ class ChatListView extends StatelessWidget { } } -class _ChatListViewBody extends StatelessWidget { +class _ChatListViewBody extends StatefulWidget { final ChatListController controller; + const _ChatListViewBody(this.controller, {Key key}) : super(key: key); @override - Widget build(BuildContext context) => StreamBuilder( - stream: Matrix.of(context) + State<_ChatListViewBody> createState() => _ChatListViewBodyState(); +} + +class _ChatListViewBodyState extends State<_ChatListViewBody> { + // the matrix sync stream + StreamSubscription _subscription; + 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 = Matrix.of(context) .client - .onSync - .stream - .where((s) => s.hasRoomUpdate) - .rateLimit(const Duration(seconds: 1)), - builder: (context, snapshot) { - return Builder( - builder: (context) { - if (controller.waitForFirstSync && - Matrix.of(context).client.prevBatch != null) { - final rooms = Matrix.of(context) - .client - .rooms - .where(controller.roomCheck) - .toList(); - if (rooms.isEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.maps_ugc_outlined, - size: 80, - color: Colors.grey, - ), - Center( - child: Text( - L10n.of(context).startYourFirstChat, - textAlign: TextAlign.start, - style: const TextStyle( - color: Colors.grey, - fontSize: 16, - ), - ), - ), - ], - ); - } - return ListView.builder( - controller: controller.scrollController, - itemCount: rooms.length, - itemBuilder: (BuildContext context, int i) { - return ChatListItem( - rooms[i], - selected: controller.selectedRoomIds.contains(rooms[i].id), - onTap: controller.selectMode == SelectMode.select - ? () => controller.toggleSelection(rooms[i].id) - : null, - onLongPress: () => controller.toggleSelection(rooms[i].id), - activeChat: controller.activeChat == rooms[i].id, - ); - }, - ); - } else { - return ListView( - children: List.filled( - 8, - const _DummyChat(), + .rooms + .where(widget.controller.roomCheck) + .toList(); + if (rooms.isEmpty) { + child = Column( + key: const ValueKey(null), + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.maps_ugc_outlined, + size: 80, + color: Colors.grey, + ), + Center( + child: Text( + L10n.of(context).startYourFirstChat, + textAlign: TextAlign.start, + style: const TextStyle( + color: Colors.grey, + fontSize: 16, ), - ); - } - }, + ), + ), + ], ); - }); + } + child = ListView.builder( + key: ValueKey(Matrix.of(context).client.userID.toString() + + widget.controller.activeSpaceId.toString()), + controller: widget.controller.scrollController, + itemCount: rooms.length, + itemBuilder: (BuildContext context, int 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 { + child = ListView( + key: const ValueKey(false), + children: List.filled( + 8, + const _DummyChat(), + ), + ); + } + 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; + } } class _DummyChat extends StatefulWidget { diff --git a/pubspec.lock b/pubspec.lock index 0c9bdc89..49ddbfe7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,12 +37,12 @@ packages: source: hosted version: "0.7.0" animations: - dependency: transitive + dependency: "direct main" description: name: animations url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" ansicolor: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" audioplayers: dependency: "direct main" description: @@ -140,7 +140,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -811,7 +811,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" matrix: dependency: "direct main" description: @@ -1383,21 +1383,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.17.10" + version: "1.17.12" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.4.2" timezone: dependency: transitive description: @@ -1551,7 +1551,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" video_player: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c7086753..fb024ee7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: adaptive_dialog: ^1.1.0 adaptive_theme: ^2.2.0 + animations: ^2.0.2 audioplayers: ^0.20.1 blurhash_dart: ^1.1.0 cached_network_image: ^3.1.0