✨ Full functional message chat
This commit is contained in:
parent
2716690c41
commit
9a2e0756b8
@ -15,6 +15,7 @@ import 'package:solian/router.dart';
|
|||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/chat/chat_message.dart';
|
import 'package:solian/widgets/chat/chat_message.dart';
|
||||||
|
import 'package:solian/widgets/chat/chat_message_action.dart';
|
||||||
import 'package:solian/widgets/chat/chat_message_input.dart';
|
import 'package:solian/widgets/chat/chat_message_input.dart';
|
||||||
|
|
||||||
class ChannelChatScreen extends StatefulWidget {
|
class ChannelChatScreen extends StatefulWidget {
|
||||||
@ -117,17 +118,21 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
case 'messages.update':
|
case 'messages.update':
|
||||||
final payload = Message.fromJson(event.payload!);
|
final payload = Message.fromJson(event.payload!);
|
||||||
if (payload.channelId == _channel?.id) {
|
if (payload.channelId == _channel?.id) {
|
||||||
_pagingController.itemList
|
final idx = _pagingController.itemList
|
||||||
?.map((x) => x.id == payload.id ? payload : x)
|
?.indexWhere((x) => x.uuid == payload.uuid);
|
||||||
.toList();
|
if (idx != null) {
|
||||||
|
_pagingController.itemList?[idx] = payload;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'messages.burnt':
|
case 'messages.burnt':
|
||||||
final payload = Message.fromJson(event.payload!);
|
final payload = Message.fromJson(event.payload!);
|
||||||
if (payload.channelId == _channel?.id) {
|
if (payload.channelId == _channel?.id) {
|
||||||
_pagingController.itemList = _pagingController.itemList
|
final idx = _pagingController.itemList
|
||||||
?.where((x) => x.id != payload.id)
|
?.indexWhere((x) => x.uuid != payload.uuid);
|
||||||
.toList();
|
if (idx != null) {
|
||||||
|
_pagingController.itemList?.removeAt(idx - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -142,6 +147,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
return a.createdAt.difference(b.createdAt).inMinutes <= 3;
|
return a.createdAt.difference(b.createdAt).inMinutes <= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message? _messageToReplying;
|
||||||
|
Message? _messageToEditing;
|
||||||
|
|
||||||
Widget chatHistoryBuilder(context, item, index) {
|
Widget chatHistoryBuilder(context, item, index) {
|
||||||
bool isMerged = false, hasMerged = false;
|
bool isMerged = false, hasMerged = false;
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
@ -158,16 +166,31 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
}
|
}
|
||||||
return InkWell(
|
return InkWell(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: !isMerged ? 8 : 0,
|
|
||||||
bottom: !hasMerged ? 8 : 0,
|
|
||||||
),
|
|
||||||
child: ChatMessage(
|
child: ChatMessage(
|
||||||
item: item,
|
item: item,
|
||||||
isMerged: isMerged,
|
isMerged: isMerged,
|
||||||
|
).paddingOnly(
|
||||||
|
top: !isMerged ? 8 : 0,
|
||||||
|
bottom: !hasMerged ? 8 : 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onLongPress: () {},
|
onLongPress: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChatMessageAction(
|
||||||
|
channel: _channel!,
|
||||||
|
realm: _channel!.realm,
|
||||||
|
item: item,
|
||||||
|
onEdit: () {
|
||||||
|
setState(() => _messageToEditing = item);
|
||||||
|
},
|
||||||
|
onReply: () {
|
||||||
|
setState(() => _messageToReplying = item);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,6 +277,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
child: ChatMessageInput(
|
child: ChatMessageInput(
|
||||||
|
edit: _messageToEditing,
|
||||||
|
reply: _messageToReplying,
|
||||||
realm: widget.realm,
|
realm: widget.realm,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
channel: _channel!,
|
channel: _channel!,
|
||||||
@ -262,6 +287,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
_pagingController.itemList?.insert(0, item);
|
_pagingController.itemList?.insert(0, item);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onReset: () {
|
||||||
|
setState(() {
|
||||||
|
_messageToReplying = null;
|
||||||
|
_messageToEditing = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -48,7 +48,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
void promptLeaveChannel() async {
|
void promptLeaveChannel() async {
|
||||||
final did = await showDialog(
|
final did = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ChannelDeletion(
|
builder: (context) => ChannelDeletionDialog(
|
||||||
channel: widget.channel,
|
channel: widget.channel,
|
||||||
realm: widget.realm,
|
realm: widget.realm,
|
||||||
isOwned: _isOwned,
|
isOwned: _isOwned,
|
||||||
|
@ -46,7 +46,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
void promptLeaveChannel() async {
|
void promptLeaveChannel() async {
|
||||||
final did = await showDialog(
|
final did = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RealmDeletion(
|
builder: (context) => RealmDeletionDialog(
|
||||||
realm: widget.realm,
|
realm: widget.realm,
|
||||||
isOwned: _isOwned,
|
isOwned: _isOwned,
|
||||||
),
|
),
|
||||||
|
@ -149,6 +149,10 @@ class SolianMessages extends Translations {
|
|||||||
'messageDecoding': 'Decoding...',
|
'messageDecoding': 'Decoding...',
|
||||||
'messageDecodeFailed': 'Unable to decode: @message',
|
'messageDecodeFailed': 'Unable to decode: @message',
|
||||||
'messageInputPlaceholder': 'Message @channel',
|
'messageInputPlaceholder': 'Message @channel',
|
||||||
|
'messageActionList': 'Actions of Message',
|
||||||
|
'messageDeletionConfirm': 'Confirm message deletion',
|
||||||
|
'messageDeletionConfirmCaption':
|
||||||
|
'Are your sure to delete message @id? This action cannot be undone!',
|
||||||
},
|
},
|
||||||
'zh_CN': {
|
'zh_CN': {
|
||||||
'hide': '隐藏',
|
'hide': '隐藏',
|
||||||
@ -287,6 +291,9 @@ class SolianMessages extends Translations {
|
|||||||
'messageDecoding': '解码信息中…',
|
'messageDecoding': '解码信息中…',
|
||||||
'messageDecodeFailed': '解码信息失败:@message',
|
'messageDecodeFailed': '解码信息失败:@message',
|
||||||
'messageInputPlaceholder': '在 @channel 发信息',
|
'messageInputPlaceholder': '在 @channel 发信息',
|
||||||
|
'messageActionList': '消息的操作',
|
||||||
|
'messageDeletionConfirm': '确认删除消息',
|
||||||
|
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@ import 'package:solian/models/channel.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
class ChannelDeletion extends StatefulWidget {
|
class ChannelDeletionDialog extends StatefulWidget {
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
final String realm;
|
final String realm;
|
||||||
final bool isOwned;
|
final bool isOwned;
|
||||||
|
|
||||||
const ChannelDeletion({
|
const ChannelDeletionDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.realm,
|
required this.realm,
|
||||||
@ -18,10 +18,10 @@ class ChannelDeletion extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelDeletion> createState() => _ChannelDeletionState();
|
State<ChannelDeletionDialog> createState() => _ChannelDeletionDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelDeletionState extends State<ChannelDeletion> {
|
class _ChannelDeletionDialogState extends State<ChannelDeletionDialog> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
Future<void> deleteChannel() async {
|
Future<void> deleteChannel() async {
|
||||||
@ -30,7 +30,7 @@ class _ChannelDeletionState extends State<ChannelDeletion> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class _ChannelDeletionState extends State<ChannelDeletion> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
|
|||||||
void getMembers() async {
|
void getMembers() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
|
|
||||||
final resp = await client
|
final resp = await client
|
||||||
@ -76,7 +76,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
|
|
||||||
class ChatMessage extends StatelessWidget {
|
class ChatMessage extends StatelessWidget {
|
||||||
final Message item;
|
final Message item;
|
||||||
|
final bool isContentPreviewing;
|
||||||
final bool isCompact;
|
final bool isCompact;
|
||||||
final bool isMerged;
|
final bool isMerged;
|
||||||
final bool isHasMerged;
|
final bool isHasMerged;
|
||||||
@ -17,6 +18,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
const ChatMessage({
|
const ChatMessage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
|
this.isContentPreviewing = false,
|
||||||
this.isMerged = false,
|
this.isMerged = false,
|
||||||
this.isHasMerged = false,
|
this.isHasMerged = false,
|
||||||
this.isCompact = false,
|
this.isCompact = false,
|
||||||
@ -94,7 +96,9 @@ class ChatMessage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget widget;
|
Widget widget;
|
||||||
if (isMerged) {
|
if (isContentPreviewing) {
|
||||||
|
widget = buildContent();
|
||||||
|
} else if (isMerged) {
|
||||||
widget = buildContent().paddingOnly(left: 52);
|
widget = buildContent().paddingOnly(left: 52);
|
||||||
} else if (isCompact) {
|
} else if (isCompact) {
|
||||||
widget = Row(
|
widget = Row(
|
||||||
@ -139,6 +143,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddingSymmetric(horizontal: 12),
|
).paddingSymmetric(horizontal: 12),
|
||||||
|
if (item.attachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
parentId: item.uuid,
|
parentId: item.uuid,
|
||||||
attachmentsId: item.attachments ?? List.empty(),
|
attachmentsId: item.attachments ?? List.empty(),
|
||||||
|
127
lib/widgets/chat/chat_message_action.dart
Normal file
127
lib/widgets/chat/chat_message_action.dart
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/models/realm.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/widgets/chat/chat_message_deletion.dart';
|
||||||
|
|
||||||
|
class ChatMessageAction extends StatefulWidget {
|
||||||
|
final Channel channel;
|
||||||
|
final Realm? realm;
|
||||||
|
final Message item;
|
||||||
|
final Function? onEdit;
|
||||||
|
final Function? onReply;
|
||||||
|
|
||||||
|
const ChatMessageAction({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
required this.realm,
|
||||||
|
required this.item,
|
||||||
|
this.onEdit,
|
||||||
|
this.onReply,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatMessageAction> createState() => _ChatMessageActionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatMessageActionState extends State<ChatMessageAction> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
bool _canModifyContent = false;
|
||||||
|
|
||||||
|
void checkAbleToModifyContent() async {
|
||||||
|
final AuthProvider provider = Get.find();
|
||||||
|
if (!await provider.isAuthorized) return;
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final prof = await provider.getProfile();
|
||||||
|
setState(() {
|
||||||
|
_canModifyContent =
|
||||||
|
prof.body?['id'] == widget.item.sender.account.externalId;
|
||||||
|
_isBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
checkAbleToModifyContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'messageActionList'.tr,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'#${widget.item.id.toString().padLeft(8, '0')}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
|
||||||
|
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const FaIcon(FontAwesomeIcons.reply, size: 20),
|
||||||
|
title: Text('reply'.tr),
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.onReply != null) widget.onReply!();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_canModifyContent)
|
||||||
|
const Divider(thickness: 0.3, height: 0.3)
|
||||||
|
.paddingSymmetric(vertical: 16),
|
||||||
|
if (_canModifyContent)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: Text('edit'.tr),
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.onEdit != null) widget.onEdit!();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_canModifyContent)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
|
title: Text('delete'.tr),
|
||||||
|
onTap: () async {
|
||||||
|
final value = await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChatMessageDeletionDialog(
|
||||||
|
channel: widget.channel,
|
||||||
|
realm: widget.realm,
|
||||||
|
item: widget.item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (value != null) {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
73
lib/widgets/chat/chat_message_deletion.dart
Normal file
73
lib/widgets/chat/chat_message_deletion.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/models/realm.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
|
class ChatMessageDeletionDialog extends StatefulWidget {
|
||||||
|
final Channel channel;
|
||||||
|
final Realm? realm;
|
||||||
|
final Message item;
|
||||||
|
|
||||||
|
const ChatMessageDeletionDialog({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
required this.realm,
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatMessageDeletionDialog> createState() =>
|
||||||
|
_ChatMessageDeletionDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatMessageDeletionDialogState extends State<ChatMessageDeletionDialog> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
void performAction() async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) return;
|
||||||
|
|
||||||
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final scope = (widget.realm?.alias.isNotEmpty ?? false)
|
||||||
|
? widget.realm?.alias
|
||||||
|
: 'global';
|
||||||
|
final resp = await client.delete(
|
||||||
|
'/api/channels/$scope/${widget.channel.alias}/messages/${widget.item.id}',
|
||||||
|
);
|
||||||
|
if (resp.statusCode == 200) {
|
||||||
|
Navigator.pop(context, resp.body);
|
||||||
|
} else {
|
||||||
|
context.showErrorDialog(resp.bodyString);
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('messageDeletionConfirm'.tr),
|
||||||
|
content: Text('messageDeletionConfirmCaption'.trParams({
|
||||||
|
'id': '#${widget.item.id}',
|
||||||
|
})),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.pop(context, false),
|
||||||
|
child: Text('cancel'.tr),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : performAction,
|
||||||
|
child: Text('confirm'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:solian/models/message.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/widgets/attachments/attachment_publish.dart';
|
import 'package:solian/widgets/attachments/attachment_publish.dart';
|
||||||
|
import 'package:solian/widgets/chat/chat_message.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChatMessageInput extends StatefulWidget {
|
class ChatMessageInput extends StatefulWidget {
|
||||||
@ -16,6 +17,7 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
final Channel channel;
|
final Channel channel;
|
||||||
final String realm;
|
final String realm;
|
||||||
final Function(Message) onSent;
|
final Function(Message) onSent;
|
||||||
|
final Function()? onReset;
|
||||||
|
|
||||||
const ChatMessageInput({
|
const ChatMessageInput({
|
||||||
super.key,
|
super.key,
|
||||||
@ -25,6 +27,7 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
required this.channel,
|
required this.channel,
|
||||||
required this.realm,
|
required this.realm,
|
||||||
required this.onSent,
|
required this.onSent,
|
||||||
|
this.onReset,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -99,7 +102,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
senderId: sender.id,
|
senderId: sender.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
widget.onSent(message);
|
if (widget.edit == null) widget.onSent(message);
|
||||||
resetInput();
|
resetInput();
|
||||||
|
|
||||||
Response resp;
|
Response resp;
|
||||||
@ -121,21 +124,53 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetInput() {
|
void resetInput() {
|
||||||
|
if (widget.onReset != null) widget.onReset!();
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void syncWidget() {
|
||||||
|
if (widget.edit != null) {
|
||||||
|
_textController.text = widget.edit!.content['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant ChatMessageInput oldWidget) {
|
||||||
|
syncWidget();
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const double height = 56;
|
const borderRadius = BorderRadius.all(Radius.circular(20));
|
||||||
const borderRadius = BorderRadius.all(Radius.circular(height / 2));
|
|
||||||
|
final notifyBannerActions = [
|
||||||
|
TextButton(
|
||||||
|
onPressed: resetInput,
|
||||||
|
child: Text('cancel'.tr),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
child: SizedBox(
|
child: Column(
|
||||||
height: height,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (widget.edit != null)
|
||||||
|
MaterialBanner(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
content: ChatMessage(
|
||||||
|
item: widget.edit!,
|
||||||
|
isContentPreviewing: true,
|
||||||
|
),
|
||||||
|
actions: notifyBannerActions,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 56,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -170,6 +205,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
],
|
],
|
||||||
).paddingOnly(left: 16, right: 4),
|
).paddingOnly(left: 16, right: 4),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,21 +5,21 @@ import 'package:solian/models/realm.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
class RealmDeletion extends StatefulWidget {
|
class RealmDeletionDialog extends StatefulWidget {
|
||||||
final Realm realm;
|
final Realm realm;
|
||||||
final bool isOwned;
|
final bool isOwned;
|
||||||
|
|
||||||
const RealmDeletion({
|
const RealmDeletionDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.realm,
|
required this.realm,
|
||||||
required this.isOwned,
|
required this.isOwned,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RealmDeletion> createState() => _RealmDeletionState();
|
State<RealmDeletionDialog> createState() => _RealmDeletionDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RealmDeletionState extends State<RealmDeletion> {
|
class _RealmDeletionDialogState extends State<RealmDeletionDialog> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
Future<void> deleteChannel() async {
|
Future<void> deleteChannel() async {
|
||||||
@ -28,7 +28,7 @@ class _RealmDeletionState extends State<RealmDeletion> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class _RealmDeletionState extends State<RealmDeletion> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
|
|||||||
void getMembers() async {
|
void getMembers() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
|
|
||||||
final resp = await client.get('/api/realms/${widget.realm.alias}/members');
|
final resp = await client.get('/api/realms/${widget.realm.alias}/members');
|
||||||
@ -73,7 +73,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final client = GetConnect();
|
final client = GetConnect(maxAuthRetries: 3);
|
||||||
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
client.httpClient.baseUrl = ServiceFinder.services['passport'];
|
||||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user