Merge branch 'krille/remove-status-feature' into 'main'

Krille/remove status feature

See merge request ChristianPauly/fluffychat-flutter!245
This commit is contained in:
Christian Pauly 2020-10-28 06:23:50 +00:00
commit 6b844a168e
13 changed files with 373 additions and 652 deletions

View File

@ -1,66 +1,15 @@
import 'package:famedlysdk/famedlysdk.dart'; 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/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../avatar.dart'; import '../avatar.dart';
import '../matrix.dart'; import '../user_bottom_sheet.dart';
class ParticipantListItem extends StatelessWidget { class ParticipantListItem extends StatelessWidget {
final User user; final User user;
const ParticipantListItem(this.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var membershipBatch = <Membership, String>{ var membershipBatch = <Membership, String>{
@ -74,87 +23,43 @@ class ParticipantListItem extends StatelessWidget {
: user.powerLevel >= 50 : user.powerLevel >= 50
? L10n.of(context).moderator ? L10n.of(context).moderator
: ''; : '';
var items = <PopupMenuEntry<String>>[];
if (user.id != Matrix.of(context).client.userID) { return ListTile(
items.add( onTap: () => showModalBottomSheet(
PopupMenuItem( context: context,
child: Text(L10n.of(context).sendAMessage), value: 'message'), builder: (context) => UserBottomSheet(
); user: user,
}
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: <Widget>[
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()),
), ),
title: Row(
children: <Widget>[
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()),
); );
} }
} }

View File

@ -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<Profile>(
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: <Widget>[
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,
),
),
),
],
),
),
);
});
}
}

View File

@ -7,7 +7,6 @@ import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/utils/firebase_controller.dart'; import 'package:fluffychat/utils/firebase_controller.dart';
import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/user_status.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -22,7 +21,6 @@ import '../main.dart';
import '../utils/app_route.dart'; import '../utils/app_route.dart';
import '../utils/beautify_string_extension.dart'; import '../utils/beautify_string_extension.dart';
import '../utils/famedlysdk_store.dart'; import '../utils/famedlysdk_store.dart';
import '../utils/presence_extension.dart';
import '../views/key_verification.dart'; import '../views/key_verification.dart';
import '../utils/platform_infos.dart'; import '../utils/platform_infos.dart';
import 'avatar.dart'; import 'avatar.dart';
@ -91,7 +89,6 @@ class MatrixState extends State<Matrix> {
await client.connect(); await client.connect();
final firstLoginState = await initLoginState; final firstLoginState = await initLoginState;
if (firstLoginState == LoginState.logged) { if (firstLoginState == LoginState.logged) {
_cleanUpUserStatus(userStatuses);
if (PlatformInfos.isMobile) { if (PlatformInfos.isMobile) {
await FirebaseController.setupFirebase( await FirebaseController.setupFirebase(
this, this,
@ -123,7 +120,6 @@ class MatrixState extends State<Matrix> {
StreamSubscription onNotification; StreamSubscription onNotification;
StreamSubscription<html.Event> onFocusSub; StreamSubscription<html.Event> onFocusSub;
StreamSubscription<html.Event> onBlurSub; StreamSubscription<html.Event> onBlurSub;
StreamSubscription onPresenceSub;
void onJitsiCall(EventUpdate eventUpdate) { void onJitsiCall(EventUpdate eventUpdate) {
final event = Event.fromJson( final event = Event.fromJson(
@ -246,9 +242,6 @@ class MatrixState extends State<Matrix> {
importantStateEvents: <String>{ importantStateEvents: <String>{
'im.ponies.room_emotes', // we want emotes to work properly 'im.ponies.room_emotes', // we want emotes to work properly
}); });
onPresenceSub ??= client.onPresence.stream
.where((p) => p.isUserStatus)
.listen(_storeUserStatus);
onJitsiCallSub ??= client.onEvent.stream onJitsiCallSub ??= client.onEvent.stream
.where((e) => .where((e) =>
e.type == 'timeline' && e.type == 'timeline' &&
@ -330,64 +323,11 @@ class MatrixState extends State<Matrix> {
super.initState(); super.initState();
} }
List<UserStatus> 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<UserStatus>.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<UserStatus> 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 @override
void dispose() { void dispose() {
onRoomKeyRequestSub?.cancel(); onRoomKeyRequestSub?.cancel();
onKeyVerificationRequestSub?.cancel(); onKeyVerificationRequestSub?.cancel();
onJitsiCallSub?.cancel(); onJitsiCallSub?.cancel();
onPresenceSub?.cancel();
onNotification?.cancel(); onNotification?.cancel();
onFocusSub?.cancel(); onFocusSub?.cancel();
onBlurSub?.cancel(); onBlurSub?.cancel();

View File

@ -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 = <PopupMenuEntry<String>>[];
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),
),
],
),
),
),
),
),
);
}
}

View File

@ -895,6 +895,11 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"mention": "Mention",
"@mention": {
"type": "text",
"placeholders": {}
},
"messageWillBeRemovedWarning": "Message will be removed for all participants", "messageWillBeRemovedWarning": "Message will be removed for all participants",
"@messageWillBeRemovedWarning": { "@messageWillBeRemovedWarning": {
"type": "text", "type": "text",
@ -992,6 +997,21 @@
"type": "text", "type": "text",
"placeholders": {} "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": "Online Key Backup is enabled",
"@onlineKeyBackupEnabled": { "@onlineKeyBackupEnabled": {
"type": "text", "type": "text",

View File

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

View File

@ -4,21 +4,37 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'date_time_extension.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 { 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) { String getLocalizedStatusMessage(BuildContext context) {
if (presence.statusMsg?.isNotEmpty ?? false) { if (presence.statusMsg?.isNotEmpty ?? false) {
return presence.statusMsg; return presence.statusMsg;
} }
if (presence.lastActiveAgo != null ?? presence.lastActiveAgo != 0) {
return L10n.of(context).lastActiveAgo(
DateTime.fromMillisecondsSinceEpoch(presence.lastActiveAgo)
.localizedTimeShort(context));
}
if (presence.currentlyActive) { if (presence.currentlyActive) {
return L10n.of(context).currentlyActive; return L10n.of(context).currentlyActive;
} }
return L10n.of(context).lastSeenLongTimeAgo; return presence.presence.getLocalized(context);
} }
} }

View File

@ -1,21 +0,0 @@
class UserStatus {
String statusMsg;
String userId;
int receivedAt;
UserStatus();
UserStatus.fromJson(Map<String, dynamic> json) {
statusMsg = json['status_msg'];
userId = json['user_id'];
receivedAt = json['received_at'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['status_msg'] = statusMsg;
data['user_id'] = userId;
data['received_at'] = receivedAt;
return data;
}
}

View File

@ -15,6 +15,7 @@ import 'package:fluffychat/components/encryption_button.dart';
import 'package:fluffychat/components/list_items/message.dart'; import 'package:fluffychat/components/list_items/message.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/components/reply_content.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/config/app_emojis.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_locals.dart';
@ -476,16 +477,22 @@ class _ChatState extends State<_Chat> {
return ListTile( return ListTile(
leading: Avatar(room.avatar, room.displayname), leading: Avatar(room.avatar, room.displayname),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
onTap: room.isDirectChat && room.directChatPresence == null onTap: room.isDirectChat
? null ? () => showModalBottomSheet(
: room.isDirectChat context: context,
? null builder: (context) => UserBottomSheet(
: () => Navigator.of(context).push( user: room
AppRoute.defaultRoute( .getUserByMXIDSync(room.directChatMatrixID),
context, onMention: () => sendController.text +=
ChatDetails(room), ' ${room.directChatMatrixID}',
), ),
), )
: () => Navigator.of(context).push(
AppRoute.defaultRoute(
context,
ChatDetails(room),
),
),
title: Text( title: Text(
room.getLocalizedDisplayname( room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context))), MatrixLocals(L10n.of(context))),
@ -684,10 +691,17 @@ class _ChatState extends State<_Chat> {
onSwipe: (direction) => replyAction( onSwipe: (direction) => replyAction(
replyTo: filteredEvents[i - 1]), replyTo: filteredEvents[i - 1]),
child: Message(filteredEvents[i - 1], child: Message(filteredEvents[i - 1],
onAvatarTab: (Event event) { onAvatarTab: (Event event) =>
sendController.text += showModalBottomSheet(
' ${event.senderId}'; context: context,
}, builder: (context) =>
UserBottomSheet(
user: event.sender,
onMention: () =>
sendController.text +=
' ${event.senderId}',
),
),
onSelect: (Event event) { onSelect: (Event event) {
if (!event.redacted) { if (!event.redacted) {
if (selectedEvents if (selectedEvents

View File

@ -3,18 +3,15 @@ import 'dart:io';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/matrix_api.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/connection_status_header.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.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/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/utils/platform_infos.dart';
import 'package:fluffychat/views/status_view.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:share/share.dart';
import '../components/adaptive_page_layout.dart'; import '../components/adaptive_page_layout.dart';
import '../components/list_items/chat_list_item.dart'; import '../components/list_items/chat_list_item.dart';
@ -198,29 +195,23 @@ class _ChatListState extends State<ChatList> {
); );
} }
void _setStatus(BuildContext context, {bool fromDrawer = false}) async { void _setStatus(BuildContext context) async {
if (fromDrawer) Navigator.of(context).pop(); Navigator.of(context).pop();
final ownProfile = await SimpleDialogs(context) final statusMsg = await SimpleDialogs(context).enterText(
.tryRequestWithLoadingDialog(Matrix.of(context).client.ownProfile); titleText: L10n.of(context).setStatus,
String composeText; labelText: L10n.of(context).setStatus,
if (Matrix.of(context).shareContent != null && hintText: L10n.of(context).statusExampleMessage,
Matrix.of(context).shareContent['msgtype'] == 'm.text') { multiLine: true,
composeText = Matrix.of(context).shareContent['body']; );
Matrix.of(context).shareContent = null; if (statusMsg?.isEmpty ?? true) return;
} final client = Matrix.of(context).client;
if (ownProfile is Profile) { await SimpleDialogs(context).tryRequestWithLoadingDialog(
await Navigator.of(context).push( client.sendPresence(
MaterialPageRoute( client.userID,
builder: (_) => StatusView( PresenceType.online,
composeMode: true, statusMsg: statusMsg,
avatarUrl: ownProfile.avatarUrl, ),
displayname: ownProfile.displayname ?? );
Matrix.of(context).client.userID.localpart,
composeText: composeText,
),
),
);
}
return; return;
} }
@ -302,8 +293,7 @@ class _ChatListState extends State<ChatList> {
ListTile( ListTile(
leading: Icon(Icons.edit), leading: Icon(Icons.edit),
title: Text(L10n.of(context).setStatus), title: Text(L10n.of(context).setStatus),
onTap: () => onTap: () => _setStatus(context),
_setStatus(context, fromDrawer: true),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
@ -338,9 +328,11 @@ class _ChatListState extends State<ChatList> {
title: Text(L10n.of(context).inviteContact), title: Text(L10n.of(context).inviteContact),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Share.share(L10n.of(context).inviteText( FluffyShare.share(
Matrix.of(context).client.userID, L10n.of(context).inviteText(
'https://matrix.to/#/${Matrix.of(context).client.userID}')); Matrix.of(context).client.userID,
'https://matrix.to/#/${Matrix.of(context).client.userID}'),
context);
}, },
), ),
], ],
@ -422,31 +414,14 @@ class _ChatListState extends State<ChatList> {
), ),
floatingActionButton: AdaptivePageLayout.columnMode(context) floatingActionButton: AdaptivePageLayout.columnMode(context)
? null ? null
: Column( : FloatingActionButton(
mainAxisSize: MainAxisSize.min, child: Icon(Icons.add),
children: [ backgroundColor: Theme.of(context).primaryColor,
FloatingActionButton( onPressed: () => Navigator.of(context)
heroTag: null, .pushAndRemoveUntil(
child: Icon( AppRoute.defaultRoute(
Icons.edit, context, NewPrivateChatView()),
color: Theme.of(context).primaryColor, (r) => r.isFirst),
),
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( body: Column(
children: [ children: [
@ -506,94 +481,28 @@ class _ChatListState extends State<ChatList> {
final totalCount = final totalCount =
rooms.length + publicRoomsCount; rooms.length + publicRoomsCount;
return ListView.separated( return ListView.separated(
controller: _scrollController, controller: _scrollController,
separatorBuilder: (BuildContext context, separatorBuilder: (BuildContext context,
int i) => int i) =>
i == totalCount - publicRoomsCount i == totalCount - publicRoomsCount
? ListTile( ? ListTile(
title: Text( title: Text(
L10n.of(context) L10n.of(context)
.publicRooms + .publicRooms +
':', ':',
style: TextStyle( style: TextStyle(
fontWeight: fontWeight:
FontWeight.bold, FontWeight.bold,
color: Theme.of(context) color: Theme.of(context)
.primaryColor, .primaryColor,
),
), ),
) ),
: Container(), )
itemCount: totalCount + 1, : Container(),
itemBuilder: itemCount: totalCount,
(BuildContext context, int i) { itemBuilder: (BuildContext context,
if (i == 0) { int i) =>
final displayPresences = i < rooms.length
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
? ChatListItem( ? ChatListItem(
rooms[i], rooms[i],
selected: _selectedRoomIds selected: _selectedRoomIds
@ -614,8 +523,9 @@ class _ChatListState extends State<ChatList> {
) )
: PublicRoomListItem( : PublicRoomListItem(
publicRoomsResponse publicRoomsResponse
.chunk[i - rooms.length]); .chunk[i - rooms.length],
}); ),
);
} else { } else {
return Center( return Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),

View File

@ -7,9 +7,9 @@ import 'package:fluffychat/components/avatar.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/components/matrix.dart'; import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:share/share.dart';
import 'chat.dart'; import 'chat.dart';
import 'chat_list.dart'; import 'chat_list.dart';
@ -204,9 +204,10 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
Icons.share, Icons.share,
size: 16, size: 16,
), ),
onTap: () => Share.share(L10n.of(context).inviteText( onTap: () => FluffyShare.share(
Matrix.of(context).client.userID, L10n.of(context).inviteText(Matrix.of(context).client.userID,
'https://matrix.to/#/${Matrix.of(context).client.userID}')), 'https://matrix.to/#/${Matrix.of(context).client.userID}'),
context),
title: Text( title: Text(
'${L10n.of(context).yourOwnUsername}:', '${L10n.of(context).yourOwnUsername}:',
style: TextStyle( style: TextStyle(

View File

@ -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),
),
);
}
}

View File

@ -1095,5 +1095,5 @@ packages:
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
sdks: 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" flutter: ">=1.20.0 <2.0.0"