Big refactoring

This commit is contained in:
Christian Pauly 2020-06-18 13:39:24 +02:00
parent 35beaa4492
commit 1bd36023e7
11 changed files with 523 additions and 461 deletions

View file

@ -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

View file

@ -0,0 +1 @@
6c5611b14df049743797687d0807922a

View file

@ -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

View file

@ -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;

View file

@ -167,7 +167,12 @@ class SimpleDialogs {
children: <Widget>[
CircularProgressIndicator(),
SizedBox(width: 16),
Text(L10n.of(context).loadingPleaseWait),
Expanded(
child: Text(
L10n.of(context).loadingPleaseWait,
overflow: TextOverflow.ellipsis,
maxLines: 1,
)),
],
),
),

View file

@ -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<String, Profile> _presences = {};
Future<Profile> _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<Profile>(
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: <Widget>[
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: <Widget>[
SizedBox(height: 9),
Avatar(avatarUrl, displayname),
Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
displayname,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
),
],
),
),
);

View file

@ -3,8 +3,18 @@ import 'package:famedlysdk/famedlysdk.dart';
extension ClientPresenceExtension on Client {
List<Presence> 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<String, Profile> presencesCache = {};
Future<Profile> requestProfileCached(String senderId) async {
presencesCache[senderId] ??= await getProfileFromUserId(senderId);
return presencesCache[senderId];
}
}

View file

@ -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: <Widget>[
if (_loadingHistory) LinearProgressIndicator(),
Expanded(
child: FutureBuilder<bool>(
future: getTimeline(),
builder: (BuildContext context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
Column(
children: <Widget>[
if (_loadingHistory) LinearProgressIndicator(),
Expanded(
child: FutureBuilder<bool>(
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: <Widget>[
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: <Widget>[
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
? <Widget>[
Container(
height: 56,
child: FlatButton(
onPressed: () =>
forwardEventsAction(context),
child: Row(
children: <Widget>[
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: <Widget>[
Text(
L10n.of(context).reply),
Icon(Icons
.keyboard_arrow_right),
],
),
),
)
: Container(
height: 56,
child: FlatButton(
onPressed: () =>
sendAgainAction(),
child: Row(
children: <Widget>[
Text(L10n.of(context)
.tryToSendAgain),
SizedBox(width: 4),
Icon(Icons.send, size: 16),
],
),
),
)
: Container(),
]
: <Widget>[
if (!kIsWeb && inputText.isEmpty)
PopupMenuButton<String>(
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) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
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<String>(
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<String>(
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<String>(
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
? <Widget>[
Container(
height: 56,
child: FlatButton(
onPressed: () =>
forwardEventsAction(context),
child: Row(
children: <Widget>[
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: <Widget>[
Text(L10n.of(context).reply),
Icon(Icons
.keyboard_arrow_right),
],
),
),
)
: Container(
height: 56,
child: FlatButton(
onPressed: () =>
sendAgainAction(),
child: Row(
children: <Widget>[
Text(L10n.of(context)
.tryToSendAgain),
SizedBox(width: 4),
Icon(Icons.send, size: 16),
],
),
),
)
: Container(),
]
: <Widget>[
if (!kIsWeb && inputText.isEmpty)
PopupMenuButton<String>(
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) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
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<String>(
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<String>(
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<String>(
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(),
],
),
],
),

View file

@ -298,34 +298,61 @@ class _ChatListState extends State<ChatList> {
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<ChatList> {
? Container()
: PreferredSize(
preferredSize:
Size.fromHeight(89),
Size.fromHeight(90),
child: Container(
height: 81,
height: 82,
child: ListView.builder(
scrollDirection:
Axis.horizontal,

View file

@ -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:

View file

@ -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