Chat basis

This commit is contained in:
2024-05-26 01:21:08 +08:00
parent 657f36c1f8
commit 5b45718ebd
16 changed files with 379 additions and 28 deletions

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:get/get.dart';
import 'package:solian/models/message.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:timeago/timeago.dart' show format;
class ChatMessage extends StatelessWidget {
final Message item;
final bool isCompact;
const ChatMessage({super.key, required this.item, required this.isCompact});
Future<String?> decodeContent(Map<String, dynamic> content) async {
String? text;
if (item.type == 'm.text') {
switch (content['algorithm']) {
case 'plain':
text = content['value'];
default:
throw Exception('Unsupported algorithm');
// TODO Impl AES algorithm
}
}
return text;
}
@override
Widget build(BuildContext context) {
final hasAttachment = item.attachments?.isNotEmpty ?? false;
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(content: item.sender.account.avatar),
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),
FutureBuilder(
future: decodeContent(item.content),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Opacity(
opacity: 0.8,
child: Row(
children: [
const Icon(Icons.more_horiz),
Text('messageDecoding'.tr)
],
),
)
.animate(onPlay: (c) => c.repeat())
.fade(begin: 0, end: 1);
} else if (snapshot.hasError) {
return Opacity(
opacity: 0.9,
child: Row(
children: [
const Icon(Icons.close),
Text(
'messageDecodeFailed'.trParams(
{'message': snapshot.error.toString()}),
)
],
),
);
}
return Markdown(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
data: snapshot.data ?? '',
padding: const EdgeInsets.all(0),
).paddingOnly(
left: 12,
right: 12,
top: 2,
bottom: hasAttachment ? 4 : 0,
);
}),
],
),
),
],
),
],
);
}
}

View File

@ -15,18 +15,21 @@ class _AppNavigationBottomBarState extends State<AppNavigationBottomBar> {
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
items: AppNavigation.destinations.map(
(e) => BottomNavigationBarItem(
icon: e.icon,
label: e.label,
),
).toList(),
items: AppNavigation.destinations
.map(
(e) => BottomNavigationBarItem(
icon: e.icon,
label: e.label,
),
)
.toList(),
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
currentIndex: _selectedIndex,
showUnselectedLabels: false,
onTap: (idx) {
setState(() => _selectedIndex = idx);
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
AppRouter.instance
.pushReplacementNamed(AppNavigation.destinations[idx].page);
},
);
}

View File

@ -27,7 +27,8 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
selectedIndex: _selectedIndex,
onDestinationSelected: (idx) {
setState(() => _selectedIndex = idx);
AppRouter.instance.pushNamed(AppNavigation.destinations[idx].page);
AppRouter.instance
.pushReplacementNamed(AppNavigation.destinations[idx].page);
},
);
}