diff --git a/CHANGELOG.md b/CHANGELOG.md index 73b6e02..31f5033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Version 0.15.0 +### Features: +- New room list app bar design +- Chat app bar transparent +### Changes: +- Show presences of users sharing a direct chat +- Big refactoring + # Version 0.14.0 - 2020-05-20 ### Features: - Implement image viewer diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id new file mode 100644 index 0000000..f7f5f2a --- /dev/null +++ b/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +6c5611b14df049743797687d0807922a \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bc93b20..efcbfd1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,52 +1,48 @@ PODS: - file_picker (0.0.1): - Flutter - - Firebase/Core (6.21.0): + - Firebase/Core (6.27.0): - Firebase/CoreOnly - - FirebaseAnalytics (= 6.4.0) - - Firebase/CoreOnly (6.21.0): - - FirebaseCore (= 6.6.5) - - Firebase/Messaging (6.21.0): + - FirebaseAnalytics (= 6.6.1) + - Firebase/CoreOnly (6.27.0): + - FirebaseCore (= 6.8.0) + - Firebase/Messaging (6.27.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 4.3.0) + - FirebaseMessaging (~> 4.5.0) - firebase_messaging (0.0.1): - Firebase/Core - Firebase/Messaging - Flutter - - FirebaseAnalytics (6.4.0): - - FirebaseCore (~> 6.6) - - FirebaseInstallations (~> 1.1) - - GoogleAppMeasurement (= 6.4.0) + - FirebaseAnalytics (6.6.1): + - FirebaseCore (~> 6.8) + - FirebaseInstallations (~> 1.4) + - GoogleAppMeasurement (= 6.6.1) - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/Network (~> 6.0) - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - FirebaseAnalyticsInterop (1.5.0) - - FirebaseCore (6.6.5): - - FirebaseCoreDiagnostics (~> 1.2) - - FirebaseCoreDiagnosticsInterop (~> 1.2) + - nanopb (~> 1.30905.0) + - FirebaseCore (6.8.0): + - FirebaseCoreDiagnostics (~> 1.3) - GoogleUtilities/Environment (~> 6.5) - GoogleUtilities/Logger (~> 6.5) - - FirebaseCoreDiagnostics (1.2.2): - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleDataTransportCCTSupport (~> 2.0) + - FirebaseCoreDiagnostics (1.4.0): + - GoogleDataTransportCCTSupport (~> 3.1) - GoogleUtilities/Environment (~> 6.5) - GoogleUtilities/Logger (~> 6.5) - - nanopb (~> 0.3.901) - - FirebaseCoreDiagnosticsInterop (1.2.0) - - FirebaseInstallations (1.1.1): - - FirebaseCore (~> 6.6) - - GoogleUtilities/UserDefaults (~> 6.5) + - nanopb (~> 1.30905.0) + - FirebaseInstallations (1.4.0): + - FirebaseCore (~> 6.8) + - GoogleUtilities/Environment (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.6) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (4.3.2): - - FirebaseCore (~> 6.6) + - FirebaseInstanceID (4.4.0): + - FirebaseCore (~> 6.8) - FirebaseInstallations (~> 1.0) - GoogleUtilities/Environment (~> 6.5) - GoogleUtilities/UserDefaults (~> 6.5) - - FirebaseMessaging (4.3.0): - - FirebaseAnalyticsInterop (~> 1.5) - - FirebaseCore (~> 6.6) + - FirebaseMessaging (4.5.0): + - FirebaseCore (~> 6.8) - FirebaseInstanceID (~> 4.3) - GoogleUtilities/AppDelegateSwizzler (~> 6.5) - GoogleUtilities/Environment (~> 6.5) @@ -54,6 +50,8 @@ PODS: - GoogleUtilities/UserDefaults (~> 6.5) - Protobuf (>= 3.9.2, ~> 3.9) - Flutter (1.0.0) + - flutter_keyboard_visibility (0.7.0): + - Flutter - flutter_local_notifications (0.0.1): - Flutter - flutter_secure_storage (3.3.1): @@ -62,42 +60,45 @@ PODS: - Flutter - FMDB (2.7.5): - FMDB/standard (= 2.7.5) + - FMDB/SQLCipher (2.7.5): + - SQLCipher - FMDB/standard (2.7.5) - - GoogleAppMeasurement (6.4.0): + - GoogleAppMeasurement (6.6.1): - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/Network (~> 6.0) - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - GoogleDataTransport (5.1.0) - - GoogleDataTransportCCTSupport (2.0.1): - - GoogleDataTransport (~> 5.1) - - nanopb (~> 0.3.901) - - GoogleUtilities/AppDelegateSwizzler (6.5.2): + - nanopb (~> 1.30905.0) + - GoogleDataTransport (6.2.1) + - GoogleDataTransportCCTSupport (3.2.0): + - GoogleDataTransport (~> 6.1) + - nanopb (~> 1.30905.0) + - GoogleUtilities/AppDelegateSwizzler (6.6.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (6.5.2) - - GoogleUtilities/Logger (6.5.2): + - GoogleUtilities/Environment (6.6.0): + - PromisesObjC (~> 1.2) + - GoogleUtilities/Logger (6.6.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (6.5.2): + - GoogleUtilities/MethodSwizzler (6.6.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (6.5.2): + - GoogleUtilities/Network (6.6.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.5.2)" - - GoogleUtilities/Reachability (6.5.2): + - "GoogleUtilities/NSData+zlib (6.6.0)" + - GoogleUtilities/Reachability (6.6.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.5.2): + - GoogleUtilities/UserDefaults (6.6.0): - GoogleUtilities/Logger - image_picker (0.0.1): - Flutter - - nanopb (0.3.9011): - - nanopb/decode (= 0.3.9011) - - nanopb/encode (= 0.3.9011) - - nanopb/decode (0.3.9011) - - nanopb/encode (0.3.9011) + - nanopb (1.30905.0): + - nanopb/decode (= 1.30905.0) + - nanopb/encode (= 1.30905.0) + - nanopb/decode (1.30905.0) + - nanopb/encode (1.30905.0) - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) - OLMKit/olmcpp (= 3.1.0) @@ -107,8 +108,8 @@ PODS: - Flutter - path_provider (0.0.1): - Flutter - - PromisesObjC (1.2.8) - - Protobuf (3.11.4) + - PromisesObjC (1.2.9) + - Protobuf (3.12.0) - receive_sharing_intent (0.0.1): - Flutter - share (0.5.2): @@ -116,6 +117,14 @@ PODS: - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) + - sqflite_sqlcipher (0.0.1): + - Flutter + - FMDB/SQLCipher (~> 2.7.5) + - SQLCipher (4.4.0): + - SQLCipher/standard (= 4.4.0) + - SQLCipher/common (4.4.0) + - SQLCipher/standard (4.4.0): + - SQLCipher/common - url_launcher (0.0.1): - Flutter - url_launcher_macos (0.0.1): @@ -129,6 +138,7 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_sound (from `.symlinks/plugins/flutter_sound/ios`) @@ -139,6 +149,7 @@ DEPENDENCIES: - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - share (from `.symlinks/plugins/share/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - sqflite_sqlcipher (from `.symlinks/plugins/sqflite_sqlcipher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) @@ -148,10 +159,8 @@ SPEC REPOS: trunk: - Firebase - FirebaseAnalytics - - FirebaseAnalyticsInterop - FirebaseCore - FirebaseCoreDiagnostics - - FirebaseCoreDiagnosticsInterop - FirebaseInstallations - FirebaseInstanceID - FirebaseMessaging @@ -164,6 +173,7 @@ SPEC REPOS: - OLMKit - PromisesObjC - Protobuf + - SQLCipher EXTERNAL SOURCES: file_picker: @@ -172,6 +182,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: @@ -190,6 +202,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" + sqflite_sqlcipher: + :path: ".symlinks/plugins/sqflite_sqlcipher/ios" url_launcher: :path: ".symlinks/plugins/url_launcher/ios" url_launcher_macos: @@ -201,35 +215,36 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245 - Firebase: f378c80340dd41c0ad0914af740c021eb282a04b - firebase_messaging: 73b3e7dd7b3b6a7e4bdac10d5295211ca4f87f90 - FirebaseAnalytics: a1a0b3327ceb5cd5b4bacffdb293f6c909aa087d - FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae - FirebaseCore: 9f495d3afacb7b558711e6218ebb14b1c51b5802 - FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 - FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 - FirebaseInstallations: acb3216eb9784d3b1d2d2d635ff74fa892cc0c44 - FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be - FirebaseMessaging: 4ec33842d36b3319e062e51fb8b35a74f726950d + Firebase: fc4cbf6f1592636431821ef9a3c557e4dfd9f268 + firebase_messaging: cffb57ce40958c6204f03fb0c81713e4cd1e240c + FirebaseAnalytics: 0ea640473474f036cabbc2576e20c2d63671c92f + FirebaseCore: feda061cb1ee6d8ad4824f4a4a8ffbcfe284f595 + FirebaseCoreDiagnostics: 4505e4d4009b1d93f605088ee7d7764d5f0d1c84 + FirebaseInstallations: 293f567159b6d66d1c990f13bb868066096c94ec + FirebaseInstanceID: 3b119bfe90e904851218159c9a4ecb847cc51d18 + FirebaseMessaging: ad9e1a80ea64905e01a0ce1b3eb76a2944544151 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_keyboard_visibility: 6195387fb6d8f46e5cd6dda4a4154e41f800f545 flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_sound: 0e8163ceac1e00eb6d894e2ae4641ba726a2c479 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleAppMeasurement: 6e68a94d0eaeb1d73ef6b0ed4f7334e29d63ae29 - GoogleDataTransport: b29a21d813e906014ca16c00897827e40e4a24ab - GoogleDataTransportCCTSupport: 6f15a89b0ca35d6fa523e1f752ef818588885988 - GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e + GoogleAppMeasurement: 2fd5c5a56c069db635c8e7b92d4809a9591d0a69 + GoogleDataTransport: 9a8a16f79feffc7f42096743de2a7c4815e84020 + GoogleDataTransportCCTSupport: 489c1265d2c85b68187a83a911913d190012158d + GoogleUtilities: 39530bc0ad980530298e9c4af8549e991fd033b1 image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f - nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd + nanopb: c43f40fadfe79e8b8db116583945847910cbabc9 OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d - PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 - Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 + PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75 + Protobuf: 2793fcd0622a00b546c60e7cbbcc493e043e9bb9 receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 share: bae0a282aab4483288913fc4dc0b935d4b491f2e sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 + sqflite_sqlcipher: 45e72be2f26bde6ad196ff8b084123d8634ba921 + SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cb9c0c5..190a43d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -328,7 +328,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -446,7 +445,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -503,7 +501,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/lib/components/dialogs/simple_dialogs.dart b/lib/components/dialogs/simple_dialogs.dart index 4d2c816..b1fd04e 100644 --- a/lib/components/dialogs/simple_dialogs.dart +++ b/lib/components/dialogs/simple_dialogs.dart @@ -167,7 +167,12 @@ class SimpleDialogs { children: [ CircularProgressIndicator(), SizedBox(width: 16), - Text(L10n.of(context).loadingPleaseWait), + Expanded( + child: Text( + L10n.of(context).loadingPleaseWait, + overflow: TextOverflow.ellipsis, + maxLines: 1, + )), ], ), ), diff --git a/lib/components/list_items/presence_list_item.dart b/lib/components/list_items/presence_list_item.dart index e55452d..2bc1c46 100644 --- a/lib/components/list_items/presence_list_item.dart +++ b/lib/components/list_items/presence_list_item.dart @@ -1,7 +1,8 @@ import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/components/dialogs/presence_dialog.dart'; +import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/views/chat.dart'; import 'package:flutter/material.dart'; - +import '../../utils/client_presence_extension.dart'; import '../avatar.dart'; import '../matrix.dart'; @@ -10,50 +11,48 @@ class PresenceListItem extends StatelessWidget { const PresenceListItem(this.presence); - static final Map _presences = {}; - - Future _requestProfile(BuildContext context) async { - _presences[presence.senderId] ??= - await Matrix.of(context).client.getProfileFromUserId(presence.senderId); - return _presences[presence.senderId]; + void _startChatAction(BuildContext context, String userId) async { + final roomId = await User(userId, + room: Room(client: Matrix.of(context).client, id: '')) + .startDirectChat(); + await Navigator.of(context).pushAndRemoveUntil( + AppRoute.defaultRoute( + context, + ChatView(roomId), + ), + (Route r) => r.isFirst); } @override Widget build(BuildContext context) { return FutureBuilder( - future: _requestProfile(context), + future: + Matrix.of(context).client.requestProfileCached(presence.senderId), builder: (context, snapshot) { - if (!snapshot.hasData) return Container(); Uri avatarUrl; var displayname = presence.senderId.localpart; if (snapshot.hasData) { avatarUrl = snapshot.data.avatarUrl; - displayname = snapshot.data.displayname; + displayname = + snapshot.data.displayname ?? presence.senderId.localpart; } return InkWell( - onTap: () => showDialog( - context: context, - builder: (c) => PresenceDialog( - presence, - avatarUrl: avatarUrl, - displayname: displayname, - ), - child: Container( - width: 80, - child: Column( - children: [ - SizedBox(height: 9), - Avatar(avatarUrl, displayname), - Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - displayname, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), + onTap: () => _startChatAction(context, presence.senderId), + child: Container( + width: 80, + child: Column( + children: [ + SizedBox(height: 9), + Avatar(avatarUrl, displayname), + Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + displayname, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), - ], - ), + ), + ], ), ), ); diff --git a/lib/utils/client_presence_extension.dart b/lib/utils/client_presence_extension.dart index 02bdb9b..42540d1 100644 --- a/lib/utils/client_presence_extension.dart +++ b/lib/utils/client_presence_extension.dart @@ -3,8 +3,18 @@ import 'package:famedlysdk/famedlysdk.dart'; extension ClientPresenceExtension on Client { List get statusList { final statusList = presences.values.toList().reversed.toList(); - statusList.removeWhere((p) => p.presence.statusMsg?.isEmpty ?? true); + final directRooms = rooms.where((r) => r.isDirectChat).toList(); + statusList.removeWhere((p) => + directRooms.indexWhere((r) => r.directChatMatrixID == p.senderId) == + -1); statusList.reversed.toList(); return statusList; } + + static final Map presencesCache = {}; + + Future requestProfileCached(String senderId) async { + presencesCache[senderId] ??= await getProfileFromUserId(senderId); + return presencesCache[senderId]; + } } diff --git a/lib/views/chat.dart b/lib/views/chat.dart index fa4d097..8d24351 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -358,7 +358,9 @@ class _ChatState extends State<_Chat> { } return Scaffold( + extendBodyBehindAppBar: true, appBar: AppBar( + backgroundColor: Theme.of(context).appBarTheme.color.withOpacity(0.9), leading: selectMode ? IconButton( icon: Icon(Icons.close), @@ -462,337 +464,328 @@ class _ChatState extends State<_Chat> { fit: BoxFit.cover, ), ), - SafeArea( - child: Column( - children: [ - if (_loadingHistory) LinearProgressIndicator(), - Expanded( - child: FutureBuilder( - future: getTimeline(), - builder: (BuildContext context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } + Column( + children: [ + if (_loadingHistory) LinearProgressIndicator(), + Expanded( + child: FutureBuilder( + future: getTimeline(), + builder: (BuildContext context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } - if (room.notificationCount != null && - room.notificationCount > 0 && - timeline != null && - timeline.events.isNotEmpty) { - room.sendReadReceipt(timeline.events.first.eventId); - } + if (room.notificationCount != null && + room.notificationCount > 0 && + timeline != null && + timeline.events.isNotEmpty) { + room.sendReadReceipt(timeline.events.first.eventId); + } - if (timeline.events.isEmpty) return Container(); + if (timeline.events.isEmpty) return Container(); - return ListView.builder( - padding: EdgeInsets.symmetric( - horizontal: max( - 0, - (MediaQuery.of(context).size.width - - AdaptivePageLayout.defaultMinWidth * - 3.5) / - 2), - ), - reverse: true, - itemCount: timeline.events.length + 2, - controller: _scrollController, - itemBuilder: (BuildContext context, int i) { - return i == timeline.events.length + 1 - ? _canLoadMore && !_loadingHistory - ? FlatButton( - child: Text( - L10n.of(context).loadMore, - style: TextStyle( - color: - Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - decoration: - TextDecoration.underline, - ), + return ListView.builder( + padding: EdgeInsets.symmetric( + horizontal: max( + 0, + (MediaQuery.of(context).size.width - + AdaptivePageLayout.defaultMinWidth * + 3.5) / + 2), + ), + reverse: true, + itemCount: timeline.events.length + 2, + controller: _scrollController, + itemBuilder: (BuildContext context, int i) { + return i == timeline.events.length + 1 + ? _canLoadMore && !_loadingHistory + ? 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, - ), + ), + 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.originServerTs - .compareTo(b.originServerTs), + ), + 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), ); } - }, - longPressSelect: selectedEvents.isEmpty, - selected: selectedEvents - .contains(timeline.events[i - 1]), - timeline: timeline, - nextEvent: i >= 2 - ? timeline.events[i - 2] - : null); - }); - }, - ), + selectedEvents.sort( + (a, b) => a.originServerTs + .compareTo(b.originServerTs), + ); + } + }, + longPressSelect: selectedEvents.isEmpty, + selected: selectedEvents + .contains(timeline.events[i - 1]), + timeline: timeline, + nextEvent: i >= 2 + ? timeline.events[i - 2] + : null); + }); + }, ), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: replyEvent != null ? 56 : 0, - child: Material( - color: Theme.of(context).secondaryHeaderColor, - child: Row( - children: [ - IconButton( - icon: Icon(Icons.close), - onPressed: () => setState(() => replyEvent = null), - ), - Expanded( - child: ReplyContent(replyEvent), - ), - ], - ), - ), - ), - Divider( - height: 1, + ), + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: replyEvent != null ? 56 : 0, + child: Material( color: Theme.of(context).secondaryHeaderColor, - thickness: 1, + child: Row( + children: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () => setState(() => replyEvent = null), + ), + Expanded( + child: ReplyContent(replyEvent), + ), + ], + ), ), - room.canSendDefaultMessages && - room.membership == Membership.join - ? Container( - decoration: BoxDecoration( - color: Theme.of(context) - .backgroundColor - .withOpacity(0.8), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: selectMode - ? [ - Container( - height: 56, - child: FlatButton( - onPressed: () => - forwardEventsAction(context), - child: Row( - children: [ - Icon(Icons.keyboard_arrow_left), - Text(L10n.of(context).forward), - ], - ), - ), - ), - selectedEvents.length == 1 - ? selectedEvents.first.status > 0 - ? Container( - height: 56, - child: FlatButton( - onPressed: () => replyAction(), - child: Row( - children: [ - Text( - L10n.of(context).reply), - Icon(Icons - .keyboard_arrow_right), - ], - ), - ), - ) - : Container( - height: 56, - child: FlatButton( - onPressed: () => - sendAgainAction(), - child: Row( - children: [ - Text(L10n.of(context) - .tryToSendAgain), - SizedBox(width: 4), - Icon(Icons.send, size: 16), - ], - ), - ), - ) - : Container(), - ] - : [ - if (!kIsWeb && inputText.isEmpty) - PopupMenuButton( - icon: Icon(Icons.add), - onSelected: (String choice) async { - if (choice == 'file') { - sendFileAction(context); - } else if (choice == 'image') { - sendImageAction(context); - } - if (choice == 'camera') { - openCameraAction(context); - } - if (choice == 'voice') { - voiceMessageAction(context); - } - }, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: 'file', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - child: Icon(Icons.attachment), - ), - title: - Text(L10n.of(context).sendFile), - contentPadding: EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'image', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - child: Icon(Icons.image), - ), - title: Text( - L10n.of(context).sendImage), - contentPadding: EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'camera', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.purple, - foregroundColor: Colors.white, - child: Icon(Icons.camera_alt), - ), - title: Text( - L10n.of(context).openCamera), - contentPadding: EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'voice', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - child: Icon(Icons.mic), - ), - title: Text( - L10n.of(context).voiceMessage), - contentPadding: EdgeInsets.all(0), - ), - ), + ), + Divider( + height: 1, + color: Theme.of(context).secondaryHeaderColor, + thickness: 1, + ), + room.canSendDefaultMessages && room.membership == Membership.join + ? Container( + decoration: BoxDecoration( + color: + Theme.of(context).backgroundColor.withOpacity(0.8), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: selectMode + ? [ + Container( + height: 56, + child: FlatButton( + onPressed: () => + forwardEventsAction(context), + child: Row( + children: [ + Icon(Icons.keyboard_arrow_left), + Text(L10n.of(context).forward), ], ), - EncryptionButton(room), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0), - child: InputBar( - room: room, - minLines: 1, - maxLines: kIsWeb ? 1 : 8, - keyboardType: kIsWeb - ? TextInputType.text - : TextInputType.multiline, - onSubmitted: (String text) { - send(); - FocusScope.of(context) - .requestFocus(inputFocus); - }, - focusNode: inputFocus, - controller: sendController, - decoration: InputDecoration( - hintText: - L10n.of(context).writeAMessage, - border: InputBorder.none, + ), + ), + selectedEvents.length == 1 + ? selectedEvents.first.status > 0 + ? Container( + height: 56, + child: FlatButton( + onPressed: () => replyAction(), + child: Row( + children: [ + Text(L10n.of(context).reply), + Icon(Icons + .keyboard_arrow_right), + ], + ), + ), + ) + : Container( + height: 56, + child: FlatButton( + onPressed: () => + sendAgainAction(), + child: Row( + children: [ + Text(L10n.of(context) + .tryToSendAgain), + SizedBox(width: 4), + Icon(Icons.send, size: 16), + ], + ), + ), + ) + : Container(), + ] + : [ + if (!kIsWeb && inputText.isEmpty) + PopupMenuButton( + icon: Icon(Icons.add), + onSelected: (String choice) async { + if (choice == 'file') { + sendFileAction(context); + } else if (choice == 'image') { + sendImageAction(context); + } + if (choice == 'camera') { + openCameraAction(context); + } + if (choice == 'voice') { + voiceMessageAction(context); + } + }, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + value: 'file', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + child: Icon(Icons.attachment), + ), + title: + Text(L10n.of(context).sendFile), + contentPadding: EdgeInsets.all(0), ), - onChanged: (String text) { - typingCoolDown?.cancel(); - typingCoolDown = - Timer(Duration(seconds: 2), () { - typingCoolDown = null; - currentlyTyping = false; - room.sendTypingInfo(false); - }); - typingTimeout ??= - Timer(Duration(seconds: 30), () { - typingTimeout = null; - currentlyTyping = false; - }); - if (!currentlyTyping) { - currentlyTyping = true; - room.sendTypingInfo(true, - timeout: Duration(seconds: 30) - .inMilliseconds); - } - setState(() => inputText = text); - }, ), + PopupMenuItem( + value: 'image', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + child: Icon(Icons.image), + ), + title: + Text(L10n.of(context).sendImage), + contentPadding: EdgeInsets.all(0), + ), + ), + PopupMenuItem( + value: 'camera', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.purple, + foregroundColor: Colors.white, + child: Icon(Icons.camera_alt), + ), + title: + Text(L10n.of(context).openCamera), + contentPadding: EdgeInsets.all(0), + ), + ), + PopupMenuItem( + value: 'voice', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + child: Icon(Icons.mic), + ), + title: Text( + L10n.of(context).voiceMessage), + contentPadding: EdgeInsets.all(0), + ), + ), + ], + ), + EncryptionButton(room), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0), + child: InputBar( + room: room, + minLines: 1, + maxLines: kIsWeb ? 1 : 8, + keyboardType: kIsWeb + ? TextInputType.text + : TextInputType.multiline, + onSubmitted: (String text) { + send(); + FocusScope.of(context) + .requestFocus(inputFocus); + }, + focusNode: inputFocus, + controller: sendController, + decoration: InputDecoration( + hintText: + L10n.of(context).writeAMessage, + border: InputBorder.none, + ), + onChanged: (String text) { + typingCoolDown?.cancel(); + typingCoolDown = + Timer(Duration(seconds: 2), () { + typingCoolDown = null; + currentlyTyping = false; + room.sendTypingInfo(false); + }); + typingTimeout ??= + Timer(Duration(seconds: 30), () { + typingTimeout = null; + currentlyTyping = false; + }); + if (!currentlyTyping) { + currentlyTyping = true; + room.sendTypingInfo(true, + timeout: Duration(seconds: 30) + .inMilliseconds); + } + setState(() => inputText = text); + }, ), ), - if (!kIsWeb && inputText.isEmpty) - IconButton( - icon: Icon(Icons.mic), - onPressed: () => - voiceMessageAction(context), - ), - if (kIsWeb || inputText.isNotEmpty) - IconButton( - icon: Icon(Icons.send), - onPressed: () => send(), - ), - ], - ), - ) - : Container(), - ], - ), + ), + if (!kIsWeb && inputText.isEmpty) + IconButton( + icon: Icon(Icons.mic), + onPressed: () => + voiceMessageAction(context), + ), + if (kIsWeb || inputText.isNotEmpty) + IconButton( + icon: Icon(Icons.send), + onPressed: () => send(), + ), + ], + ), + ) + : Container(), + ], ), ], ), diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index b8617ad..831e5dd 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -298,34 +298,61 @@ class _ChatListState extends State { onPressed: () => Matrix.of(context).shareContent = null, ), + automaticallyImplyLeading: false, titleSpacing: 0, title: selectMode == SelectMode.share ? Text(L10n.of(context).share) : Container( - padding: EdgeInsets.all(8), height: 42, - margin: EdgeInsets.only(right: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(90), - ), - child: TextField( - autocorrect: false, - controller: searchController, - decoration: InputDecoration( - suffixIcon: loadingPublicRooms - ? Container( - alignment: Alignment.centerRight, - child: Container( - width: 20, - height: 20, - child: CircularProgressIndicator(), + margin: EdgeInsets.symmetric(horizontal: 8), + child: Material( + elevation: 5, + borderRadius: BorderRadius.circular(7), + child: Padding( + padding: EdgeInsets.all(8), + child: Row( + children: [ + Builder( + builder: (context) => IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.menu), + onPressed: () => + Scaffold.of(context).openDrawer(), + ), + ), + Expanded( + child: TextField( + autocorrect: false, + controller: searchController, + decoration: InputDecoration( + contentPadding: EdgeInsets.all(9), + border: InputBorder.none, + hintText: + L10n.of(context).searchForAChat, ), - ) - : Icon(Icons.search), - contentPadding: EdgeInsets.all(9), - border: InputBorder.none, - hintText: L10n.of(context).searchForAChat, + ), + ), + loadingPublicRooms + ? Container( + alignment: Alignment.centerRight, + child: Container( + width: 20, + height: 20, + child: + CircularProgressIndicator(), + ), + ) + : IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.account_circle), + onPressed: () => + Navigator.of(context).push( + AppRoute.defaultRoute( + context, + SettingsView())), + ), + ], + ), ), ), ), @@ -433,9 +460,9 @@ class _ChatListState extends State { ? Container() : PreferredSize( preferredSize: - Size.fromHeight(89), + Size.fromHeight(90), child: Container( - height: 81, + height: 82, child: ListView.builder( scrollDirection: Axis.horizontal, diff --git a/pubspec.lock b/pubspec.lock index 550ac2b..69001ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -71,6 +71,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: @@ -129,12 +136,19 @@ packages: url: "https://github.com/simolus3/moor.git" source: git version: "1.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" famedlysdk: dependency: "direct main" description: path: "." - ref: "857775cf37804440717ce797e0ed63fd39066904" - resolved-ref: "857775cf37804440717ce797e0ed63fd39066904" + ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f + resolved-ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" @@ -469,7 +483,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.9.3" package_resolver: dependency: transitive description: @@ -483,7 +497,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.7.0" path_drawing: dependency: transitive description: @@ -561,13 +575,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" random_string: dependency: "direct main" description: @@ -628,7 +635,7 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "2.0.0" source_maps: dependency: transitive description: @@ -705,21 +712,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.13.0" + version: "1.14.7" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.16" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "0.3.7" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8b4e637..27d2268 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: 857775cf37804440717ce797e0ed63fd39066904 + ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f localstorage: ^3.0.1+4 bubble: ^1.1.9+1