From 090795fa77f1b19fbd8bcb66a189ebd123b5bbd5 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sat, 3 Oct 2020 15:53:08 +0200 Subject: [PATCH] feat: Implement new status feature --- lib/components/connection_status_header.dart | 2 +- lib/components/dialogs/presence_dialog.dart | 63 -------- .../list_items/presence_list_item.dart | 109 -------------- .../list_items/status_list_item.dart | 83 +++++++++++ lib/components/matrix.dart | 61 ++++++++ lib/utils/client_presence_extension.dart | 9 +- lib/utils/presence_extension.dart | 2 + lib/utils/user_status.dart | 21 +++ lib/views/chat_list.dart | 141 ++++++++++++------ .../{presence_view.dart => status_view.dart} | 83 ++++++++--- 10 files changed, 329 insertions(+), 245 deletions(-) delete mode 100644 lib/components/dialogs/presence_dialog.dart delete mode 100644 lib/components/list_items/presence_list_item.dart create mode 100644 lib/components/list_items/status_list_item.dart create mode 100644 lib/utils/user_status.dart rename lib/views/{presence_view.dart => status_view.dart} (59%) diff --git a/lib/components/connection_status_header.dart b/lib/components/connection_status_header.dart index 705a4f5..0789f18 100644 --- a/lib/components/connection_status_header.dart +++ b/lib/components/connection_status_header.dart @@ -11,7 +11,7 @@ class ConnectionStatusHeader extends StatefulWidget { class _ConnectionStatusHeaderState extends State { StreamSubscription _onSyncSub; StreamSubscription _onSyncErrorSub; - static bool _connected = false; + static bool _connected = true; set connected(bool connected) { if (mounted) { diff --git a/lib/components/dialogs/presence_dialog.dart b/lib/components/dialogs/presence_dialog.dart deleted file mode 100644 index afcfd83..0000000 --- a/lib/components/dialogs/presence_dialog.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/utils/app_route.dart'; -import 'package:fluffychat/utils/presence_extension.dart'; -import 'package:fluffychat/views/chat.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import '../avatar.dart'; -import '../matrix.dart'; - -class PresenceDialog extends StatelessWidget { - final Uri avatarUrl; - final String displayname; - final Presence presence; - - const PresenceDialog( - this.presence, { - this.avatarUrl, - this.displayname, - Key key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: ListTile( - contentPadding: EdgeInsets.zero, - leading: Avatar(avatarUrl, displayname), - title: Text(displayname), - subtitle: Text(presence.senderId), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(presence.getLocalizedStatusMessage(context)), - ], - ), - actions: [ - if (presence.senderId != Matrix.of(context).client.userID) - FlatButton( - child: Text(L10n.of(context).sendAMessage), - onPressed: () async { - final roomId = await User( - presence.senderId, - room: Room(id: '', client: Matrix.of(context).client), - ).startDirectChat(); - await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute( - context, - ChatView(roomId), - ), - (Route r) => r.isFirst); - }, - ), - FlatButton( - child: Text(L10n.of(context).close), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ); - } -} diff --git a/lib/components/list_items/presence_list_item.dart b/lib/components/list_items/presence_list_item.dart deleted file mode 100644 index 5e4a939..0000000 --- a/lib/components/list_items/presence_list_item.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/utils/app_route.dart'; -import 'package:fluffychat/views/chat.dart'; -import 'package:fluffychat/views/presence_view.dart'; -import 'package:flutter/material.dart'; -import '../avatar.dart'; -import '../matrix.dart'; - -class PresenceListItem extends StatelessWidget { - final Room room; - - const PresenceListItem(this.room); - - 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) { - final user = room.getUserByMXIDSync(room.directChatMatrixID); - final presence = - Matrix.of(context).client.presences[room.directChatMatrixID]; - final hasStatus = presence?.presence?.statusMsg != null; - return InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => presence?.presence?.statusMsg == null - ? _startChatAction(context, user.id) - : /*showDialog( - context: context, - builder: (_) => PresenceDialog( - presence, - avatarUrl: user.avatarUrl, - displayname: user.calcDisplayname(), - ), - ),*/ - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => PresenceView( - presence: presence, - avatarUrl: user.avatarUrl, - displayname: user.calcDisplayname(), - ), - ), - ), - child: Container( - width: 76, - child: Column( - children: [ - SizedBox(height: 10), - Container( - child: Stack( - children: [ - Avatar(user.avatarUrl, user.calcDisplayname()), - if (presence?.presence?.currentlyActive == true) - Positioned( - bottom: 0, - right: 0, - child: Container( - width: 10, - height: 10, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.green, - ), - ), - ), - ], - ), - decoration: BoxDecoration( - border: Border.all( - width: 1, - color: !hasStatus - ? Theme.of(context).secondaryHeaderColor - : Theme.of(context).primaryColor, - ), - borderRadius: BorderRadius.circular(80), - ), - padding: EdgeInsets.all(2), - ), - Padding( - padding: const EdgeInsets.only(left: 6.0, top: 0.0, right: 6.0), - child: Text( - user.calcDisplayname().trim().split(' ').first, - overflow: TextOverflow.clip, - maxLines: 1, - style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyText2 - .color - .withOpacity(hasStatus ? 1 : 0.66), - fontSize: 13, - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/components/list_items/status_list_item.dart b/lib/components/list_items/status_list_item.dart new file mode 100644 index 0000000..7ed9680 --- /dev/null +++ b/lib/components/list_items/status_list_item.dart @@ -0,0 +1,83 @@ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/user_status.dart'; +import 'package:fluffychat/views/status_view.dart'; +import 'package:flutter/material.dart'; +import '../avatar.dart'; +import '../matrix.dart'; + +class StatusListItem extends StatelessWidget { + final UserStatus status; + + const StatusListItem(this.status, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + return FutureBuilder( + future: client.getProfileFromUserId(status.userId), + builder: (context, snapshot) { + final profile = + snapshot.data ?? Profile(client.userID, Uri.parse('')); + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => StatusView( + status: status, + avatarUrl: profile.avatarUrl, + displayname: profile.displayname, + ), + ), + ), + child: Container( + width: 76, + child: Column( + children: [ + SizedBox(height: 10), + Container( + child: Stack( + children: [ + Avatar(profile.avatarUrl, profile.displayname), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.green, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Theme.of(context).primaryColor, + ), + borderRadius: BorderRadius.circular(80), + ), + padding: EdgeInsets.all(2), + ), + Padding( + padding: + const EdgeInsets.only(left: 6.0, top: 0.0, right: 6.0), + child: Text( + profile.displayname.trim().split(' ').first, + overflow: TextOverflow.clip, + maxLines: 1, + style: TextStyle( + color: Theme.of(context).textTheme.bodyText2.color, + fontSize: 13, + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index e704c4d..2306404 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:famedlysdk/encryption.dart'; @@ -7,6 +8,7 @@ import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/utils/firebase_controller.dart'; import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/user_status.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -17,6 +19,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../utils/app_route.dart'; import '../utils/beautify_string_extension.dart'; import '../utils/famedlysdk_store.dart'; +import '../utils/presence_extension.dart'; import '../views/key_verification.dart'; import 'avatar.dart'; @@ -106,6 +109,7 @@ class MatrixState extends State { StreamSubscription onNotification; StreamSubscription onFocusSub; StreamSubscription onBlurSub; + StreamSubscription onPresenceSub; void onJitsiCall(EventUpdate eventUpdate) { final event = Event.fromJson( @@ -191,6 +195,16 @@ class MatrixState extends State { @override void initState() { store = widget.store ?? Store(); + store.getItem('fluffychat.user_statuses').then( + (json) { + userStatuses = json == null + ? [] + : (jsonDecode(json)['user_statuses'] as List) + .map((j) => UserStatus.fromJson(j)) + .toList(); + _cleanUpUserStatus(); + }, + ); if (widget.client == null) { debugPrint('[Matrix] Init matrix client'); final Set verificationMethods = { @@ -206,6 +220,9 @@ class MatrixState extends State { importantStateEvents: { 'im.ponies.room_emotes', // we want emotes to work properly }); + onPresenceSub ??= client.onPresence.stream + .where((p) => p.isUserStatus) + .listen(_storeUserStatus); onJitsiCallSub ??= client.onEvent.stream .where((e) => e.type == 'timeline' && @@ -213,6 +230,7 @@ class MatrixState extends State { e.content['content']['msgtype'] == Matrix.callNamespace && e.content['sender'] != client.userID) .listen(onJitsiCall); + onRoomKeyRequestSub ??= client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async { final room = request.room; @@ -285,11 +303,54 @@ class MatrixState extends State { super.initState(); } + List userStatuses = []; + + void _storeUserStatus(Presence presence) { + final currentStatusIndex = + userStatuses.indexWhere((u) => u.userId == presence.senderId); + final newUserStatus = UserStatus() + ..receivedAt = DateTime.now().millisecondsSinceEpoch + ..statusMsg = presence.presence.statusMsg + ..userId = presence.senderId; + if (currentStatusIndex == -1) { + userStatuses.add(newUserStatus); + } else if (userStatuses[currentStatusIndex].statusMsg != + presence.presence.statusMsg) { + if (presence.presence.statusMsg.trim().isEmpty) { + userStatuses.removeAt(currentStatusIndex); + } else { + userStatuses[currentStatusIndex] = newUserStatus; + } + } else { + return; + } + _cleanUpUserStatus(); + } + + void _cleanUpUserStatus() { + final now = DateTime.now().millisecondsSinceEpoch; + userStatuses + .removeWhere((u) => (now - u.receivedAt) > (1000 * 60 * 60 * 24)); + userStatuses.sort((a, b) => b.receivedAt.compareTo(a.receivedAt)); + if (userStatuses.length > 40) { + userStatuses.removeRange(40, userStatuses.length); + } + store.setItem( + 'fluffychat.user_statuses', + jsonEncode( + { + 'user_statuses': userStatuses.map((i) => i.toJson()).toList(), + }, + ), + ); + } + @override void dispose() { onRoomKeyRequestSub?.cancel(); onKeyVerificationRequestSub?.cancel(); onJitsiCallSub?.cancel(); + onPresenceSub?.cancel(); onNotification?.cancel(); onFocusSub?.cancel(); onBlurSub?.cancel(); diff --git a/lib/utils/client_presence_extension.dart b/lib/utils/client_presence_extension.dart index 65f1885..39e7519 100644 --- a/lib/utils/client_presence_extension.dart +++ b/lib/utils/client_presence_extension.dart @@ -1,10 +1,7 @@ import 'package:famedlysdk/famedlysdk.dart'; extension ClientPresenceExtension on Client { - static final Map presencesCache = {}; - - Future requestProfileCached(String senderId) async { - presencesCache[senderId] ??= await getProfileFromUserId(senderId); - return presencesCache[senderId]; - } + List get statuses => presences.values + .where((p) => p.presence.statusMsg?.isNotEmpty ?? false) + .toList(); } diff --git a/lib/utils/presence_extension.dart b/lib/utils/presence_extension.dart index 6aefb90..bfbd032 100644 --- a/lib/utils/presence_extension.dart +++ b/lib/utils/presence_extension.dart @@ -5,6 +5,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'date_time_extension.dart'; extension PresenceExtension on Presence { + bool get isUserStatus => presence?.statusMsg?.isNotEmpty ?? false; + String getLocalizedStatusMessage(BuildContext context) { if (presence.statusMsg?.isNotEmpty ?? false) { return presence.statusMsg; diff --git a/lib/utils/user_status.dart b/lib/utils/user_status.dart new file mode 100644 index 0000000..dadfb3f --- /dev/null +++ b/lib/utils/user_status.dart @@ -0,0 +1,21 @@ +class UserStatus { + String statusMsg; + String userId; + int receivedAt; + + UserStatus(); + + UserStatus.fromJson(Map json) { + statusMsg = json['status_msg']; + userId = json['user_id']; + receivedAt = json['received_at']; + } + + Map toJson() { + final data = {}; + data['status_msg'] = statusMsg; + data['user_id'] = userId; + data['received_at'] = receivedAt; + return data; + } +} diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index 1bbfe18..8c94933 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -3,12 +3,13 @@ import 'dart:io'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/matrix_api.dart'; +import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/connection_status_header.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; -import 'package:fluffychat/components/list_items/presence_list_item.dart'; +import 'package:fluffychat/components/list_items/status_list_item.dart'; import 'package:fluffychat/components/list_items/public_room_list_item.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/views/presence_view.dart'; +import 'package:fluffychat/views/status_view.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -197,17 +198,25 @@ class _ChatListState extends State { ); } - void _setStatus(BuildContext context) async { - Navigator.of(context).pop(); + void _setStatus(BuildContext context, {bool fromDrawer = false}) async { + if (fromDrawer) Navigator.of(context).pop(); final ownProfile = await SimpleDialogs(context) .tryRequestWithLoadingDialog(Matrix.of(context).client.ownProfile); + String composeText; + if (Matrix.of(context).shareContent != null && + Matrix.of(context).shareContent['msgtype'] == 'm.text') { + composeText = Matrix.of(context).shareContent['body']; + Matrix.of(context).shareContent = null; + } if (ownProfile is Profile) { await Navigator.of(context).push( MaterialPageRoute( - builder: (_) => PresenceView( + builder: (_) => StatusView( composeMode: true, avatarUrl: ownProfile.avatarUrl, - displayname: ownProfile.displayname, + displayname: ownProfile.displayname ?? + Matrix.of(context).client.userID.localpart, + composeText: composeText, ), ), ); @@ -293,7 +302,8 @@ class _ChatListState extends State { ListTile( leading: Icon(Icons.edit), title: Text(L10n.of(context).setStatus), - onTap: () => _setStatus(context), + onTap: () => + _setStatus(context, fromDrawer: true), ), Divider(height: 1), ListTile( @@ -414,14 +424,32 @@ class _ChatListState extends State { (AdaptivePageLayout.columnMode(context) || selectMode != SelectMode.normal) ? null - : FloatingActionButton( - child: Icon(Icons.add), - backgroundColor: Theme.of(context).primaryColor, - onPressed: () => Navigator.of(context) - .pushAndRemoveUntil( - AppRoute.defaultRoute( - context, NewPrivateChatView()), - (r) => r.isFirst), + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + heroTag: null, + child: Icon( + Icons.edit, + color: Theme.of(context).primaryColor, + ), + elevation: 1, + backgroundColor: + Theme.of(context).secondaryHeaderColor, + onPressed: () => _setStatus(context), + ), + SizedBox(height: 16.0), + FloatingActionButton( + child: Icon(Icons.add), + backgroundColor: + Theme.of(context).primaryColor, + onPressed: () => Navigator.of(context) + .pushAndRemoveUntil( + AppRoute.defaultRoute( + context, NewPrivateChatView()), + (r) => r.isFirst), + ), + ], ), body: Column( children: [ @@ -432,7 +460,8 @@ class _ChatListState extends State { .client .onSync .stream - .where((s) => s.hasRoomUpdate), + .where((s) => + s.hasRoomUpdate || s.hasPresenceUpdate), builder: (context, snapshot) { return FutureBuilder( future: waitForFirstSync(context), @@ -475,19 +504,6 @@ class _ChatListState extends State { 0); final totalCount = rooms.length + publicRoomsCount; - final directChats = rooms - .where((r) => r.isDirectChat) - .toList(); - final presences = - Matrix.of(context).client.presences; - directChats.sort((a, b) => presences[ - b.directChatMatrixID] - ?.presence - ?.statusMsg != - null - ? 1 - : b.lastEvent.originServerTs.compareTo( - a.lastEvent.originServerTs)); return ListView.separated( controller: _scrollController, separatorBuilder: (BuildContext context, @@ -511,31 +527,70 @@ class _ChatListState extends State { itemBuilder: (BuildContext context, int i) { if (i == 0) { - final displayPresences = directChats - .isNotEmpty && - selectMode == SelectMode.normal; + final displayPresences = + Matrix.of(context) + .userStatuses + .isNotEmpty && + selectMode == + SelectMode.normal; + final displayShareStatus = + selectMode == + SelectMode.share && + Matrix.of(context) + .shareContent[ + 'msgtype'] == + 'm.text'; return Column( mainAxisSize: MainAxisSize.min, children: [ AnimatedContainer( duration: Duration( milliseconds: 500), - height: - displayPresences ? 78 : 0, - child: !displayPresences - ? null - : ListView.builder( + height: displayPresences + ? 78 + : displayShareStatus + ? 56 + : 0, + child: displayPresences + ? ListView.builder( scrollDirection: Axis.horizontal, - itemCount: directChats - .length, + itemCount: + Matrix.of(context) + .userStatuses + .length, itemBuilder: (BuildContext context, int i) => - PresenceListItem( - directChats[ - i]), - ), + StatusListItem(Matrix + .of(context) + .userStatuses[i]), + ) + : displayShareStatus + ? ListTile( + leading: + CircleAvatar( + radius: Avatar + .defaultSize / + 2, + backgroundColor: + Theme.of( + context) + .secondaryHeaderColor, + child: Icon( + Icons.edit, + color: Theme.of( + context) + .primaryColor, + ), + ), + title: Text(L10n.of( + context) + .setStatus), + onTap: () => + _setStatus( + context)) + : null, ), ], ); diff --git a/lib/views/presence_view.dart b/lib/views/status_view.dart similarity index 59% rename from lib/views/presence_view.dart rename to lib/views/status_view.dart index d48c1de..d2b0bea 100644 --- a/lib/views/presence_view.dart +++ b/lib/views/status_view.dart @@ -2,32 +2,37 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/matrix.dart'; +import 'package:fluffychat/utils/url_launcher.dart'; +import 'package:fluffychat/utils/user_status.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/utils/presence_extension.dart'; +import 'package:matrix_link_text/link_text.dart'; import 'chat.dart'; -class PresenceView extends StatelessWidget { +class StatusView extends StatelessWidget { final Uri avatarUrl; final String displayname; - final Presence presence; + final UserStatus status; final bool composeMode; - final TextEditingController _composeController = TextEditingController(); + final String composeText; + final TextEditingController _composeController; - PresenceView({ + StatusView({ this.composeMode = false, - this.presence, + this.status, this.avatarUrl, this.displayname, + this.composeText, Key key, - }) : super(key: key); + }) : _composeController = TextEditingController(text: composeText), + super(key: key); void _sendMessageAction(BuildContext context) async { final roomId = await User( - presence.senderId, + status.userId, room: Room(id: '', client: Matrix.of(context).client), ).startDirectChat(); await Navigator.of(context).pushAndRemoveUntil( @@ -48,9 +53,22 @@ class PresenceView extends StatelessWidget { await Navigator.of(context).popUntil((Route r) => r.isFirst); } + void _removeStatusAction(BuildContext context) async { + final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( + Matrix.of(context).client.sendPresence( + Matrix.of(context).client.userID, + PresenceType.online, + statusMsg: + ' ', // Send this empty String make sure that all other devices will get an update + ), + ); + if (success == false) return; + await Navigator.of(context).popUntil((Route r) => r.isFirst); + } + @override Widget build(BuildContext context) { - if (composeMode == false && presence == null) { + if (composeMode == false && status == null) { throw ('If composeMode is null then the presence must be not null!'); } final padding = const EdgeInsets.only( @@ -81,10 +99,20 @@ class PresenceView extends StatelessWidget { style: TextStyle(color: Colors.white), ), subtitle: Text( - presence?.senderId ?? Matrix.of(context).client.userID, + status?.userId ?? Matrix.of(context).client.userID, style: TextStyle(color: Colors.white), ), ), + actions: + !composeMode && status.userId == Matrix.of(context).client.userID + ? [ + IconButton( + icon: Icon(Icons.archive), + onPressed: () => _removeStatusAction(context), + color: Colors.white, + ), + ] + : null, ), body: Container( alignment: Alignment.center, @@ -121,27 +149,36 @@ class PresenceView extends StatelessWidget { shrinkWrap: true, padding: padding, children: [ - Text( - presence.getLocalizedStatusMessage(context), + LinkText( + text: status.statusMsg, textAlign: TextAlign.center, - style: TextStyle( + textStyle: TextStyle( fontSize: 30, color: Colors.white, ), + linkStyle: TextStyle( + fontSize: 30, + color: Colors.white70, + decoration: TextDecoration.underline, + ), + onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), ), ], ), ), - floatingActionButton: FloatingActionButton.extended( - backgroundColor: Theme.of(context).primaryColor, - icon: Icon(composeMode ? Icons.edit : Icons.message_outlined), - label: Text(composeMode - ? L10n.of(context).setStatus - : L10n.of(context).sendAMessage), - onPressed: () => composeMode - ? _setStatusAction(context) - : _sendMessageAction(context), - ), + floatingActionButton: + !composeMode && status.userId == Matrix.of(context).client.userID + ? null + : FloatingActionButton.extended( + backgroundColor: Theme.of(context).primaryColor, + icon: Icon(composeMode ? Icons.edit : Icons.message_outlined), + label: Text(composeMode + ? L10n.of(context).setStatus + : L10n.of(context).sendAMessage), + onPressed: () => composeMode + ? _setStatusAction(context) + : _sendMessageAction(context), + ), ); } }