diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index c13fb86..a64f0ce 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -435,6 +435,8 @@ class L10n extends MatrixLocalizations { String get loadingPleaseWait => Intl.message("Loading... Please wait"); + String get loadMore => Intl.message('Load more...'); + String loadCountMoreParticipants(String count) => Intl.message( "Load $count more participants", name: "loadCountMoreParticipants", diff --git a/lib/utils/client_presence_extension.dart b/lib/utils/client_presence_extension.dart index 9f6deee..41674ef 100644 --- a/lib/utils/client_presence_extension.dart +++ b/lib/utils/client_presence_extension.dart @@ -2,9 +2,9 @@ import 'package:famedlysdk/famedlysdk.dart'; extension ClientPresenceExtension on Client { List get statusList { - final statusList = presences.values.toList(); + final statusList = presences.values.toList().reversed.toList(); statusList.removeWhere((p) => p.statusMsg?.isEmpty ?? true); - statusList.sort((a, b) => b.time.compareTo(a.time)); + statusList.reversed.toList(); return statusList; } } diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart index c3f8d2a..9fd8733 100644 --- a/lib/utils/famedlysdk_store.dart +++ b/lib/utils/famedlysdk_store.dart @@ -496,6 +496,10 @@ class ExtendedStore extends Store implements ExtendedStoreAPI { Future forgetRoom(String roomID) async { await _db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]); + await _db.rawDelete("DELETE FROM Events WHERE room_id=?", [roomID]); + await _db.rawDelete("DELETE FROM RoomStates WHERE room_id=?", [roomID]); + await _db + .rawDelete("DELETE FROM RoomAccountData WHERE room_id=?", [roomID]); return; } @@ -528,7 +532,7 @@ class ExtendedStore extends Store implements ExtendedStoreAPI { "sender": rawPresences[i]["sender"], "content": json.decode(rawPresences[i]["content"]), }; - newPresences[rawPresences[i]["type"]] = Presence.fromJson(rawPresence); + newPresences[rawPresences[i]["sender"]] = Presence.fromJson(rawPresence); } return newPresences; } diff --git a/lib/views/chat.dart b/lib/views/chat.dart index b7cd370..4ffeebc 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -79,8 +79,10 @@ class _ChatState extends State<_Chat> { String inputText = ""; + bool get _canLoadMore => timeline.events.last.type != EventTypes.RoomCreate; + void requestHistory() async { - if (timeline.events.last.type != EventTypes.RoomCreate) { + if (_canLoadMore) { setState(() => this._loadingHistory = true); await timeline.requestHistory(historyCount: _loadHistoryCount); if (mounted) setState(() => this._loadingHistory = false); @@ -450,58 +452,79 @@ class _ChatState extends State<_Chat> { return ListView.builder( reverse: true, - itemCount: timeline.events.length + 1, + itemCount: timeline.events.length + 2, controller: _scrollController, itemBuilder: (BuildContext context, int i) { - return i == 0 - ? AnimatedContainer( - height: seenByText.isEmpty ? 0 : 24, - duration: seenByText.isEmpty - ? Duration(milliseconds: 0) - : Duration(milliseconds: 500), - alignment: timeline.events.first.senderId == - client.userID - ? Alignment.topRight - : Alignment.topLeft, - child: Text( - seenByText, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).primaryColor, - ), - ), - padding: EdgeInsets.only( - left: 8, - right: 8, - bottom: 8, - ), - ) - : Message(timeline.events[i - 1], - onAvatarTab: (Event event) { - sendController.text += ' ${event.senderId}'; - }, onSelect: (Event event) { - if (!event.redacted) { - if (selectedEvents.contains(event)) { - setState( - () => selectedEvents.remove(event), - ); - } else { - setState( - () => selectedEvents.add(event), - ); - } - selectedEvents.sort( - (a, b) => a.time.compareTo(b.time), - ); - } - }, - longPressSelect: selectedEvents.isEmpty, - selected: selectedEvents - .contains(timeline.events[i - 1]), - timeline: timeline, - nextEvent: - i >= 2 ? timeline.events[i - 2] : null); + return i == timeline.events.length + 1 + ? _canLoadMore + ? FlatButton( + child: Text( + L10n.of(context).loadMore, + style: TextStyle( + color: + Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.underline, + ), + ), + onPressed: requestHistory, + ) + : Container() + : i == 0 + ? AnimatedContainer( + height: seenByText.isEmpty ? 0 : 24, + duration: seenByText.isEmpty + ? Duration(milliseconds: 0) + : Duration(milliseconds: 500), + alignment: + timeline.events.first.senderId == + client.userID + ? Alignment.topRight + : Alignment.topLeft, + child: Text( + seenByText, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: + Theme.of(context).primaryColor, + ), + ), + padding: EdgeInsets.only( + left: 8, + right: 8, + bottom: 8, + ), + ) + : Message(timeline.events[i - 1], + onAvatarTab: (Event event) { + sendController.text += + ' ${event.senderId}'; + }, onSelect: (Event event) { + if (!event.redacted) { + if (selectedEvents.contains(event)) { + setState( + () => + selectedEvents.remove(event), + ); + } else { + setState( + () => selectedEvents.add(event), + ); + } + selectedEvents.sort( + (a, b) => a.time.compareTo(b.time), + ); + } + }, + longPressSelect: selectedEvents.isEmpty, + selected: selectedEvents + .contains(timeline.events[i - 1]), + timeline: timeline, + nextEvent: i >= 2 + ? timeline.events[i - 2] + : null); }); }, ), diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index 13567ae..790885f 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -58,6 +58,8 @@ class _ChatListState extends State { bool loadingPublicRooms = false; String searchServer; + final ScrollController _scrollController = ScrollController(); + Future waitForFirstSync(BuildContext context) async { Client client = Matrix.of(context).client; if (client.prevBatch?.isEmpty ?? true) { @@ -66,8 +68,17 @@ class _ChatListState extends State { return true; } + bool _scrolledToTop = true; + @override void initState() { + _scrollController.addListener(() async { + if (_scrollController.position.pixels > 0 && _scrolledToTop) { + setState(() => _scrolledToTop = false); + } else if (_scrollController.position.pixels == 0 && !_scrolledToTop) { + setState(() => _scrolledToTop = true); + } + }); searchController.addListener(() { coolDown?.cancel(); if (searchController.text.isEmpty) { @@ -272,6 +283,7 @@ class _ChatListState extends State { ), ), appBar: AppBar( + elevation: _scrolledToTop ? 0 : null, leading: selectMode != SelectMode.share ? null : IconButton( @@ -381,6 +393,7 @@ class _ChatListState extends State { (publicRoomsResponse?.publicRooms?.length ?? 0); final int totalCount = rooms.length + publicRoomsCount; return ListView.separated( + controller: _scrollController, separatorBuilder: (BuildContext context, int i) => i == totalCount - publicRoomsCount ? Material( diff --git a/pubspec.lock b/pubspec.lock index 77f6bd0..30052a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -64,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" collection: dependency: transitive description: @@ -113,6 +120,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" famedlysdk: dependency: "direct main" description: @@ -432,7 +446,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.7.0" path_drawing: dependency: transitive description: @@ -510,13 +524,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.2" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" receive_sharing_intent: dependency: "direct main" description: