Solian/lib/widgets/chat/chat_event.dart
2024-06-29 20:25:29 +08:00

245 lines
8.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/event.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/chat/chat_event_action_log.dart';
import 'package:solian/widgets/chat/chat_event_message.dart';
import 'package:timeago/timeago.dart' show format;
class ChatEvent extends StatelessWidget {
final Event item;
final bool isContentPreviewing;
final bool isQuote;
final bool isMerged;
final bool isHasMerged;
final ChatEventController? chatController;
const ChatEvent({
super.key,
required this.item,
this.isContentPreviewing = false,
this.isMerged = false,
this.isHasMerged = false,
this.isQuote = false,
this.chatController,
});
String _formatDuration(Duration duration) {
String negativeSign = duration.isNegative ? '-' : '';
String twoDigits(int n) => n.toString().padLeft(2, '0');
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60).abs());
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).abs());
return '$negativeSign${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds';
}
Widget buildQuote() {
return FutureBuilder(
future: chatController!.getEvent(
item.body['quote_event'],
),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const SizedBox();
}
return ChatEvent(
item: snapshot.data!.data,
isMerged: false,
isQuote: true,
).paddingOnly(left: isMerged ? 52 : 0);
},
);
}
Widget buildContent() {
switch (item.type) {
case 'messages.new':
return ChatEventMessage(
item: item,
isContentPreviewing: isContentPreviewing,
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
case 'messages.edit':
return ChatEventMessageActionLog(
icon: const Icon(Icons.edit_note, size: 16),
text: 'messageEditDesc'.trParams({'id': '#${item.id}'}),
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
case 'messages.delete':
return ChatEventMessageActionLog(
icon: const Icon(Icons.cancel_schedule_send, size: 16),
text: 'messageDeleteDesc'.trParams({'id': '#${item.id}'}),
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
case 'calls.start':
return ChatEventMessageActionLog(
icon: const Icon(Icons.call_made, size: 16),
text: 'messageCallStartDesc'
.trParams({'user': '@${item.sender.account.name}'}),
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
case 'calls.end':
return ChatEventMessageActionLog(
icon: const Icon(Icons.call_received, size: 16),
text: 'messageCallEndDesc'.trParams({
'duration': _formatDuration(
Duration(seconds: item.body['last']),
),
}),
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
case 'system.changes':
return ChatEventMessageActionLog(
icon: const Icon(Icons.manage_history, size: 16),
text: item.body['text'],
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
default:
return ChatEventMessageActionLog(
icon: const Icon(Icons.error, size: 16),
text: 'messageTypeUnsupported'.trParams({'type': item.type}),
isMerged: isMerged,
isHasMerged: isHasMerged,
isQuote: isQuote,
);
}
}
Widget buildBody(BuildContext context) {
if (isContentPreviewing || (isMerged && !isQuote)) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.body['quote_event'] != null && chatController != null)
buildQuote(),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: buildContent()),
if (item.isPending)
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
],
).paddingOnly(right: 12);
} else if (isQuote) {
return Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Opacity(
opacity: 0.75,
child: FaIcon(FontAwesomeIcons.quoteLeft, size: 14),
).paddingOnly(bottom: 2.75),
const SizedBox(width: 4),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
AccountAvatar(
content: item.sender.account.avatar, radius: 9),
const SizedBox(width: 5),
Text(
item.sender.account.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
Text(format(item.createdAt, locale: 'en_short')),
],
),
buildContent().paddingOnly(left: 0.5),
],
),
),
],
).paddingOnly(left: 12, right: 12, top: 8, bottom: 4),
).paddingOnly(left: isMerged ? 52 : 0, right: 4);
} else {
return Column(
key: Key('m${item.uuid}'),
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
child: AccountAvatar(content: item.sender.account.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
context: context,
builder: (context) => AccountProfilePopup(
account: item.sender.account,
),
);
},
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
item.sender.account.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
Text(format(item.createdAt, locale: 'en_short'))
],
).paddingSymmetric(horizontal: 12),
if (item.body['quote_event'] != null &&
chatController != null)
buildQuote(),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: buildContent()),
if (item.isPending)
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
],
),
),
],
).paddingSymmetric(horizontal: 12),
],
);
}
}
@override
Widget build(BuildContext context) {
return buildBody(context);
}
}