From a241310c81cbdf1b3be04ec3c3837e8469d214b4 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Oct 2020 06:23:50 +0000 Subject: [PATCH] Krille/remove status feature --- .../list_items/participant_list_item.dart | 165 +++----------- .../list_items/status_list_item.dart | 83 ------- lib/components/matrix.dart | 60 ------ lib/components/user_bottom_sheet.dart | 186 ++++++++++++++++ lib/l10n/intl_en.arb | 20 ++ lib/utils/fluffy_share.dart | 19 ++ lib/utils/presence_extension.dart | 30 ++- lib/utils/user_status.dart | 21 -- lib/views/chat.dart | 42 ++-- lib/views/chat_list.dart | 202 +++++------------- lib/views/new_private_chat.dart | 9 +- lib/views/status_view.dart | 186 ---------------- pubspec.lock | 2 +- 13 files changed, 373 insertions(+), 652 deletions(-) delete mode 100644 lib/components/list_items/status_list_item.dart create mode 100644 lib/components/user_bottom_sheet.dart create mode 100644 lib/utils/fluffy_share.dart delete mode 100644 lib/utils/user_status.dart delete mode 100644 lib/views/status_view.dart diff --git a/lib/components/list_items/participant_list_item.dart b/lib/components/list_items/participant_list_item.dart index 5258f2c4..278fb842 100644 --- a/lib/components/list_items/participant_list_item.dart +++ b/lib/components/list_items/participant_list_item.dart @@ -1,66 +1,15 @@ import 'package:famedlysdk/famedlysdk.dart'; -import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; -import 'package:fluffychat/utils/app_route.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'; +import '../user_bottom_sheet.dart'; class ParticipantListItem extends StatelessWidget { final User user; const ParticipantListItem(this.user); - void participantAction(BuildContext context, String action) async { - switch (action) { - case 'ban': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban()); - } - break; - case 'unban': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.unban()); - } - break; - case 'kick': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick()); - } - break; - case 'admin': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(100)); - } - break; - case 'moderator': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(50)); - } - break; - case 'user': - if (await SimpleDialogs(context).askConfirmation()) { - await SimpleDialogs(context) - .tryRequestWithLoadingDialog(user.setPower(0)); - } - break; - case 'message': - final roomId = await user.startDirectChat(); - await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute( - context, - ChatView(roomId), - ), - (Route r) => r.isFirst); - break; - } - } - @override Widget build(BuildContext context) { var membershipBatch = { @@ -74,87 +23,43 @@ class ParticipantListItem extends StatelessWidget { : user.powerLevel >= 50 ? L10n.of(context).moderator : ''; - var items = >[]; - if (user.id != Matrix.of(context).client.userID) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).sendAMessage), value: 'message'), - ); - } - if (user.canChangePowerLevel && - user.room.ownPowerLevel == 100 && - user.powerLevel != 100) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).makeAnAdmin), value: 'admin'), - ); - } - if (user.canChangePowerLevel && - user.room.ownPowerLevel >= 50 && - user.powerLevel != 50) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).makeAModerator), value: 'moderator'), - ); - } - if (user.canChangePowerLevel && user.powerLevel != 0) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).revokeAllPermissions), value: 'user'), - ); - } - if (user.canKick) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).kickFromChat), value: 'kick'), - ); - } - if (user.canBan && user.membership != Membership.ban) { - items.add( - PopupMenuItem(child: Text(L10n.of(context).banFromChat), value: 'ban'), - ); - } else if (user.canBan && user.membership == Membership.ban) { - items.add( - PopupMenuItem( - child: Text(L10n.of(context).removeExile), value: 'unban'), - ); - } - return PopupMenuButton( - onSelected: (action) => participantAction(context, action), - itemBuilder: (c) => items, - child: ListTile( - title: Row( - children: [ - Text(user.calcDisplayname()), - permissionBatch.isEmpty - ? Container() - : Container( - padding: EdgeInsets.all(4), - margin: EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(8), - ), - child: Center(child: Text(permissionBatch)), - ), - membershipBatch[user.membership].isEmpty - ? Container() - : Container( - padding: EdgeInsets.all(4), - margin: EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(8), - ), - child: - Center(child: Text(membershipBatch[user.membership])), - ), - ], + return ListTile( + onTap: () => showModalBottomSheet( + context: context, + builder: (context) => UserBottomSheet( + user: user, ), - subtitle: Text(user.id), - leading: Avatar(user.avatarUrl, user.calcDisplayname()), ), + title: Row( + children: [ + Text(user.calcDisplayname()), + permissionBatch.isEmpty + ? Container() + : Container( + padding: EdgeInsets.all(4), + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: Text(permissionBatch)), + ), + membershipBatch[user.membership].isEmpty + ? Container() + : Container( + padding: EdgeInsets.all(4), + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: Text(membershipBatch[user.membership])), + ), + ], + ), + subtitle: Text(user.id), + leading: Avatar(user.avatarUrl, user.calcDisplayname()), ); } } diff --git a/lib/components/list_items/status_list_item.dart b/lib/components/list_items/status_list_item.dart deleted file mode 100644 index 1d5f2725..00000000 --- a/lib/components/list_items/status_list_item.dart +++ /dev/null @@ -1,83 +0,0 @@ -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(status.userId.localpart, null); - 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 47842b53..58b7f650 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -7,7 +7,6 @@ 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'; @@ -22,7 +21,6 @@ import '../main.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 '../utils/platform_infos.dart'; import 'avatar.dart'; @@ -91,7 +89,6 @@ class MatrixState extends State { await client.connect(); final firstLoginState = await initLoginState; if (firstLoginState == LoginState.logged) { - _cleanUpUserStatus(userStatuses); if (PlatformInfos.isMobile) { await FirebaseController.setupFirebase( this, @@ -123,7 +120,6 @@ class MatrixState extends State { StreamSubscription onNotification; StreamSubscription onFocusSub; StreamSubscription onBlurSub; - StreamSubscription onPresenceSub; void onJitsiCall(EventUpdate eventUpdate) { final event = Event.fromJson( @@ -246,9 +242,6 @@ 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' && @@ -330,64 +323,11 @@ class MatrixState extends State { super.initState(); } - List get userStatuses { - try { - return (client.accountData[userStatusesType].content['user_statuses'] - as List) - .map((json) => UserStatus.fromJson(json)) - .toList(); - } catch (_) {} - return []; - } - - void _storeUserStatus(Presence presence) { - final tmpUserStatuses = List.from(userStatuses); - 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) { - tmpUserStatuses.add(newUserStatus); - } else if (tmpUserStatuses[currentStatusIndex].statusMsg != - presence.presence.statusMsg) { - if (presence.presence.statusMsg.trim().isEmpty) { - tmpUserStatuses.removeAt(currentStatusIndex); - } else { - tmpUserStatuses[currentStatusIndex] = newUserStatus; - } - } else { - return; - } - _cleanUpUserStatus(tmpUserStatuses); - } - - void _cleanUpUserStatus(List tmpUserStatuses) { - final now = DateTime.now().millisecondsSinceEpoch; - tmpUserStatuses - .removeWhere((u) => (now - u.receivedAt) > (1000 * 60 * 60 * 24)); - tmpUserStatuses.sort((a, b) => b.receivedAt.compareTo(a.receivedAt)); - if (tmpUserStatuses.length > 40) { - tmpUserStatuses.removeRange(40, tmpUserStatuses.length); - } - if (tmpUserStatuses != userStatuses) { - client.setAccountData( - client.userID, - userStatusesType, - { - 'user_statuses': tmpUserStatuses.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/components/user_bottom_sheet.dart b/lib/components/user_bottom_sheet.dart new file mode 100644 index 00000000..d91c6bc9 --- /dev/null +++ b/lib/components/user_bottom_sheet.dart @@ -0,0 +1,186 @@ +import 'dart:math'; + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/components/adaptive_page_layout.dart'; +import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/views/chat.dart'; +import 'package:flutter/material.dart'; +import 'content_banner.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import '../utils/presence_extension.dart'; +import 'dialogs/simple_dialogs.dart'; +import 'matrix.dart'; + +class UserBottomSheet extends StatelessWidget { + final User user; + final Function onMention; + + const UserBottomSheet({Key key, @required this.user, this.onMention}) + : super(key: key); + + void participantAction(BuildContext context, String action) async { + switch (action) { + case 'mention': + Navigator.of(context).pop(); + onMention(); + break; + case 'ban': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context).tryRequestWithLoadingDialog(user.ban()); + } + break; + case 'unban': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.unban()); + } + break; + case 'kick': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context).tryRequestWithLoadingDialog(user.kick()); + } + break; + case 'admin': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(100)); + } + break; + case 'moderator': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(50)); + } + break; + case 'user': + if (await SimpleDialogs(context).askConfirmation()) { + await SimpleDialogs(context) + .tryRequestWithLoadingDialog(user.setPower(0)); + } + break; + case 'message': + final roomId = await user.startDirectChat(); + await Navigator.of(context).pushAndRemoveUntil( + AppRoute.defaultRoute( + context, + ChatView(roomId), + ), + (Route r) => r.isFirst); + break; + } + } + + @override + Widget build(BuildContext context) { + final presence = Matrix.of(context).client.presences[user.id]; + var items = >[]; + + if (onMention != null) { + items.add( + PopupMenuItem(child: Text(L10n.of(context).mention), value: 'mention'), + ); + } + if (user.id != Matrix.of(context).client.userID) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).sendAMessage), value: 'message'), + ); + } + if (user.canChangePowerLevel && + user.room.ownPowerLevel == 100 && + user.powerLevel != 100) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).makeAnAdmin), value: 'admin'), + ); + } + if (user.canChangePowerLevel && + user.room.ownPowerLevel >= 50 && + user.powerLevel != 50) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).makeAModerator), value: 'moderator'), + ); + } + if (user.canChangePowerLevel && user.powerLevel != 0) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).revokeAllPermissions), value: 'user'), + ); + } + if (user.canKick) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).kickFromChat), value: 'kick'), + ); + } + if (user.canBan && user.membership != Membership.ban) { + items.add( + PopupMenuItem(child: Text(L10n.of(context).banFromChat), value: 'ban'), + ); + } else if (user.canBan && user.membership == Membership.ban) { + items.add( + PopupMenuItem( + child: Text(L10n.of(context).removeExile), value: 'unban'), + ); + } + return Center( + child: Container( + width: min(MediaQuery.of(context).size.width, + AdaptivePageLayout.defaultMinWidth * 1.5), + child: SafeArea( + child: Material( + elevation: 4, + child: Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + elevation: 0, + backgroundColor: + Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5), + leading: IconButton( + icon: Icon(Icons.arrow_downward_outlined), + onPressed: Navigator.of(context).pop, + ), + title: Text(user.calcDisplayname()), + actions: [ + if (user.id != Matrix.of(context).client.userID) + PopupMenuButton( + itemBuilder: (_) => items, + onSelected: (action) => + participantAction(context, action), + ), + ], + ), + body: ListView( + children: [ + ContentBanner( + user.avatarUrl, + defaultIcon: Icons.person_outline, + ), + ListTile( + title: Text(L10n.of(context).username), + subtitle: Text(user.id), + trailing: Icon(Icons.share), + onTap: () => FluffyShare.share(user.id, context), + ), + if (presence != null) + ListTile( + title: Text(presence.getLocalizedStatusMessage(context)), + subtitle: + Text(presence.getLocalizedLastActiveAgo(context)), + trailing: Icon(Icons.circle, + color: presence.presence.currentlyActive + ? Colors.green + : Colors.grey), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 35ceddd6..73462e1d 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -895,6 +895,11 @@ "type": "text", "placeholders": {} }, + "mention": "Mention", + "@mention": { + "type": "text", + "placeholders": {} + }, "messageWillBeRemovedWarning": "Message will be removed for all participants", "@messageWillBeRemovedWarning": { "type": "text", @@ -992,6 +997,21 @@ "type": "text", "placeholders": {} }, + "online": "Online", + "@online": { + "type": "text", + "placeholders": {} + }, + "offline": "Offline", + "@offline": { + "type": "text", + "placeholders": {} + }, + "unavailable": "Unavailable", + "@unavailable": { + "type": "text", + "placeholders": {} + }, "onlineKeyBackupEnabled": "Online Key Backup is enabled", "@onlineKeyBackupEnabled": { "type": "text", diff --git a/lib/utils/fluffy_share.dart b/lib/utils/fluffy_share.dart new file mode 100644 index 00000000..88409a3d --- /dev/null +++ b/lib/utils/fluffy_share.dart @@ -0,0 +1,19 @@ +import 'package:bot_toast/bot_toast.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:share/share.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +abstract class FluffyShare { + static Future share(String text, BuildContext context) async { + if (PlatformInfos.isMobile) { + return Share.share(text); + } + await Clipboard.setData( + ClipboardData(text: text), + ); + BotToast.showText(text: L10n.of(context).copiedToClipboard); + return; + } +} diff --git a/lib/utils/presence_extension.dart b/lib/utils/presence_extension.dart index bfbd0325..22ea01bc 100644 --- a/lib/utils/presence_extension.dart +++ b/lib/utils/presence_extension.dart @@ -4,21 +4,37 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'date_time_extension.dart'; +extension on PresenceType { + String getLocalized(BuildContext context) { + switch (this) { + case PresenceType.online: + return L10n.of(context).online; + case PresenceType.unavailable: + return L10n.of(context).unavailable; + case PresenceType.offline: + default: + return L10n.of(context).offline; + } + } +} + extension PresenceExtension on Presence { - bool get isUserStatus => presence?.statusMsg?.isNotEmpty ?? false; + String getLocalizedLastActiveAgo(BuildContext context) { + if (presence.lastActiveAgo != null && presence.lastActiveAgo != 0) { + return L10n.of(context).lastActiveAgo(DateTime.fromMillisecondsSinceEpoch( + DateTime.now().millisecondsSinceEpoch - presence.lastActiveAgo) + .localizedTimeShort(context)); + } + return L10n.of(context).lastSeenLongTimeAgo; + } String getLocalizedStatusMessage(BuildContext context) { if (presence.statusMsg?.isNotEmpty ?? false) { return presence.statusMsg; } - if (presence.lastActiveAgo != null ?? presence.lastActiveAgo != 0) { - return L10n.of(context).lastActiveAgo( - DateTime.fromMillisecondsSinceEpoch(presence.lastActiveAgo) - .localizedTimeShort(context)); - } if (presence.currentlyActive) { return L10n.of(context).currentlyActive; } - return L10n.of(context).lastSeenLongTimeAgo; + return presence.presence.getLocalized(context); } } diff --git a/lib/utils/user_status.dart b/lib/utils/user_status.dart deleted file mode 100644 index dadfb3fd..00000000 --- a/lib/utils/user_status.dart +++ /dev/null @@ -1,21 +0,0 @@ -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.dart b/lib/views/chat.dart index 5d8d7e4b..e7a0f310 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -15,6 +15,7 @@ import 'package:fluffychat/components/encryption_button.dart'; import 'package:fluffychat/components/list_items/message.dart'; import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/reply_content.dart'; +import 'package:fluffychat/components/user_bottom_sheet.dart'; import 'package:fluffychat/config/app_emojis.dart'; import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/matrix_locals.dart'; @@ -476,16 +477,22 @@ class _ChatState extends State<_Chat> { return ListTile( leading: Avatar(room.avatar, room.displayname), contentPadding: EdgeInsets.zero, - onTap: room.isDirectChat && room.directChatPresence == null - ? null - : room.isDirectChat - ? null - : () => Navigator.of(context).push( - AppRoute.defaultRoute( - context, - ChatDetails(room), - ), - ), + onTap: room.isDirectChat + ? () => showModalBottomSheet( + context: context, + builder: (context) => UserBottomSheet( + user: room + .getUserByMXIDSync(room.directChatMatrixID), + onMention: () => sendController.text += + ' ${room.directChatMatrixID}', + ), + ) + : () => Navigator.of(context).push( + AppRoute.defaultRoute( + context, + ChatDetails(room), + ), + ), title: Text( room.getLocalizedDisplayname( MatrixLocals(L10n.of(context))), @@ -684,10 +691,17 @@ class _ChatState extends State<_Chat> { onSwipe: (direction) => replyAction( replyTo: filteredEvents[i - 1]), child: Message(filteredEvents[i - 1], - onAvatarTab: (Event event) { - sendController.text += - ' ${event.senderId}'; - }, + onAvatarTab: (Event event) => + showModalBottomSheet( + context: context, + builder: (context) => + UserBottomSheet( + user: event.sender, + onMention: () => + sendController.text += + ' ${event.senderId}', + ), + ), onSelect: (Event event) { if (!event.redacted) { if (selectedEvents diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index d56c3d43..e1f63af0 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -3,18 +3,15 @@ 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/status_list_item.dart'; import 'package:fluffychat/components/list_items/public_room_list_item.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/platform_infos.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'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; -import 'package:share/share.dart'; import '../components/adaptive_page_layout.dart'; import '../components/list_items/chat_list_item.dart'; @@ -198,29 +195,23 @@ class _ChatListState extends State { ); } - 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: (_) => StatusView( - composeMode: true, - avatarUrl: ownProfile.avatarUrl, - displayname: ownProfile.displayname ?? - Matrix.of(context).client.userID.localpart, - composeText: composeText, - ), - ), - ); - } + void _setStatus(BuildContext context) async { + Navigator.of(context).pop(); + final statusMsg = await SimpleDialogs(context).enterText( + titleText: L10n.of(context).setStatus, + labelText: L10n.of(context).setStatus, + hintText: L10n.of(context).statusExampleMessage, + multiLine: true, + ); + if (statusMsg?.isEmpty ?? true) return; + final client = Matrix.of(context).client; + await SimpleDialogs(context).tryRequestWithLoadingDialog( + client.sendPresence( + client.userID, + PresenceType.online, + statusMsg: statusMsg, + ), + ); return; } @@ -302,8 +293,7 @@ class _ChatListState extends State { ListTile( leading: Icon(Icons.edit), title: Text(L10n.of(context).setStatus), - onTap: () => - _setStatus(context, fromDrawer: true), + onTap: () => _setStatus(context), ), Divider(height: 1), ListTile( @@ -338,9 +328,11 @@ class _ChatListState extends State { title: Text(L10n.of(context).inviteContact), onTap: () { Navigator.of(context).pop(); - Share.share(L10n.of(context).inviteText( - Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}')); + FluffyShare.share( + L10n.of(context).inviteText( + Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context); }, ), ], @@ -422,31 +414,14 @@ class _ChatListState extends State { ), floatingActionButton: AdaptivePageLayout.columnMode(context) ? null - : 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), - ), - ], + : 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: [ @@ -506,94 +481,28 @@ class _ChatListState extends State { final totalCount = rooms.length + publicRoomsCount; return ListView.separated( - controller: _scrollController, - separatorBuilder: (BuildContext context, - int i) => - i == totalCount - publicRoomsCount - ? ListTile( - title: Text( - L10n.of(context) - .publicRooms + - ':', - style: TextStyle( - fontWeight: - FontWeight.bold, - color: Theme.of(context) - .primaryColor, - ), + controller: _scrollController, + separatorBuilder: (BuildContext context, + int i) => + i == totalCount - publicRoomsCount + ? ListTile( + title: Text( + L10n.of(context) + .publicRooms + + ':', + style: TextStyle( + fontWeight: + FontWeight.bold, + color: Theme.of(context) + .primaryColor, ), - ) - : Container(), - itemCount: totalCount + 1, - itemBuilder: - (BuildContext context, int i) { - if (i == 0) { - final displayPresences = - selectMode != SelectMode.share; - final displayShareStatus = - selectMode == - SelectMode.share && - Matrix.of(context) - .shareContent[ - 'msgtype'] == - 'm.text'; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - AnimatedContainer( - duration: Duration( - milliseconds: 300), - height: displayPresences - ? 78 - : displayShareStatus - ? 56 - : 0, - child: displayPresences - ? ListView.builder( - scrollDirection: - Axis.horizontal, - itemCount: - Matrix.of(context) - .userStatuses - .length, - itemBuilder: (BuildContext - context, - int 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, - ), - ], - ); - } - i--; - return i < rooms.length + ), + ) + : Container(), + itemCount: totalCount, + itemBuilder: (BuildContext context, + int i) => + i < rooms.length ? ChatListItem( rooms[i], selected: _selectedRoomIds @@ -614,8 +523,9 @@ class _ChatListState extends State { ) : PublicRoomListItem( publicRoomsResponse - .chunk[i - rooms.length]); - }); + .chunk[i - rooms.length], + ), + ); } else { return Center( child: CircularProgressIndicator(), diff --git a/lib/views/new_private_chat.dart b/lib/views/new_private_chat.dart index 46e9fd3f..66b9da42 100644 --- a/lib/views/new_private_chat.dart +++ b/lib/views/new_private_chat.dart @@ -7,9 +7,9 @@ import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:share/share.dart'; import 'chat.dart'; import 'chat_list.dart'; @@ -204,9 +204,10 @@ class _NewPrivateChatState extends State<_NewPrivateChat> { Icons.share, size: 16, ), - onTap: () => Share.share(L10n.of(context).inviteText( - Matrix.of(context).client.userID, - 'https://matrix.to/#/${Matrix.of(context).client.userID}')), + onTap: () => FluffyShare.share( + L10n.of(context).inviteText(Matrix.of(context).client.userID, + 'https://matrix.to/#/${Matrix.of(context).client.userID}'), + context), title: Text( '${L10n.of(context).yourOwnUsername}:', style: TextStyle( diff --git a/lib/views/status_view.dart b/lib/views/status_view.dart deleted file mode 100644 index 0ea3ba25..00000000 --- a/lib/views/status_view.dart +++ /dev/null @@ -1,186 +0,0 @@ -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:matrix_link_text/link_text.dart'; - -import 'chat.dart'; - -class StatusView extends StatelessWidget { - final Uri avatarUrl; - final String displayname; - final UserStatus status; - final bool composeMode; - final String composeText; - final TextEditingController _composeController; - - StatusView({ - this.composeMode = false, - this.status, - this.avatarUrl, - this.displayname, - this.composeText, - Key key, - }) : _composeController = TextEditingController(text: composeText), - super(key: key); - - void _sendMessageAction(BuildContext context) async { - final roomId = await User( - status.userId, - room: Room(id: '', client: Matrix.of(context).client), - ).startDirectChat(); - await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute( - context, - ChatView(roomId), - ), - (Route r) => r.isFirst); - } - - void _setStatusAction(BuildContext context) async { - if (_composeController.text.isEmpty) return; - await SimpleDialogs(context).tryRequestWithLoadingDialog( - Matrix.of(context).client.sendPresence( - Matrix.of(context).client.userID, PresenceType.online, - statusMsg: _composeController.text), - ); - 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 && status == null) { - throw ('If composeMode is null then the presence must be not null!'); - } - final padding = const EdgeInsets.only( - top: 16.0, - right: 16.0, - left: 16.0, - bottom: 64.0, - ); - return Scaffold( - backgroundColor: displayname.color, - extendBody: true, - appBar: AppBar( - titleSpacing: 0.0, - brightness: Brightness.dark, - leading: IconButton( - icon: Icon( - Icons.close, - color: Colors.white, - ), - onPressed: Navigator.of(context).pop, - ), - backgroundColor: Colors.transparent, - elevation: 1, - title: ListTile( - contentPadding: EdgeInsets.zero, - leading: Avatar(avatarUrl, displayname), - title: Text( - displayname, - style: TextStyle(color: Colors.white), - ), - subtitle: Text( - status?.userId ?? Matrix.of(context).client.userID, - maxLines: 1, - 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, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - displayname.color, - Theme.of(context).primaryColor, - displayname.color, - ], - ), - ), - child: composeMode - ? Padding( - padding: padding, - child: TextField( - controller: _composeController, - autofocus: true, - minLines: 1, - maxLines: 20, - style: TextStyle( - fontSize: 30, - color: Colors.white, - ), - textAlign: TextAlign.center, - decoration: InputDecoration( - border: InputBorder.none, - ), - ), - ) - : ListView( - shrinkWrap: true, - padding: padding, - children: [ - LinkText( - text: status.statusMsg, - textAlign: TextAlign.center, - textStyle: TextStyle( - fontSize: 30, - color: Colors.white, - ), - linkStyle: TextStyle( - fontSize: 30, - color: Colors.white70, - decoration: TextDecoration.underline, - ), - onLinkTap: (url) => UrlLauncher(context, url).launchUrl(), - ), - ], - ), - ), - 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), - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index aefa6799..17ea0353 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1095,5 +1095,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.10.0-110 <=2.11.0-161.0.dev" + dart: ">=2.10.0-110 <2.11.0" flutter: ">=1.20.0 <2.0.0"