Compare commits

...

9 Commits

Author SHA1 Message Date
acbc125dec 🚀 Launch 2.3.2+75 2025-02-24 23:21:06 +08:00
ad0ee971c1 Desktop mute notification
🐛 Bug fixes on tray icon
2025-02-24 22:46:02 +08:00
52d6bb083e 🐛 Fix macos titlebar not centered 2025-02-24 22:38:08 +08:00
2027eab49b 💄 Optimize displaying of message 2025-02-24 22:35:14 +08:00
566ebde1dd 🐛 Fix windows tray issue 2025-02-24 21:59:41 +08:00
9e039cc532 🐛 Fix editing message 2025-02-24 21:31:12 +08:00
c4b95d7084 🐛 Fix account settings screen error cause by locale 2025-02-24 21:25:12 +08:00
a66129a9ba 🐛 Bug fixes 2025-02-24 21:18:49 +08:00
44e1a8bf67 🚀 Launch 2.3.2+74 2025-02-23 22:45:01 +08:00
15 changed files with 214 additions and 127 deletions

View File

@ -719,6 +719,7 @@
"stickersNewDescription": "Create a new sticker belongs to this pack.", "stickersNewDescription": "Create a new sticker belongs to this pack.",
"stickersPackNew": "New Sticker Pack", "stickersPackNew": "New Sticker Pack",
"trayMenuShow": "Show", "trayMenuShow": "Show",
"trayMenuMuteNotification": "Do Not Disturb",
"update": "Update", "update": "Update",
"forceUpdate": "Force Update", "forceUpdate": "Force Update",
"forceUpdateDescription": "Force to show the application update popup, even the new version is not available." "forceUpdateDescription": "Force to show the application update popup, even the new version is not available."

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "创建一个新的贴图。", "stickersNewDescription": "创建一个新的贴图。",
"stickersPackNew": "新建贴图包", "stickersPackNew": "新建贴图包",
"trayMenuShow": "显示", "trayMenuShow": "显示",
"trayMenuMuteNotification": "静音通知",
"update": "更新", "update": "更新",
"forceUpdate": "强制更新", "forceUpdate": "强制更新",
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。"

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包", "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示", "trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新", "update": "更新",
"forceUpdate": "強制更新", "forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包", "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示", "trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新", "update": "更新",
"forceUpdate": "強制更新", "forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。" "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"

View File

@ -194,9 +194,11 @@ class ChatMessageController extends ChangeNotifier {
channelId: channel!.id, channelId: channel!.id,
createdAt: Value(message.createdAt), createdAt: Value(message.createdAt),
), ),
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom( onConflict: DoUpdate(
content: Constant(jsonEncode(message.toJson())), (_) => SnLocalChatMessageCompanion.custom(
)), content: Constant(jsonEncode(message.toJson())),
),
),
); );
} else { } else {
incomeStrandedQueue.add(message); incomeStrandedQueue.add(message);
@ -212,21 +214,21 @@ class ChatMessageController extends ChangeNotifier {
final idx = final idx =
messages.indexWhere((x) => x.id == message.relatedEventId); messages.indexWhere((x) => x.id == message.relatedEventId);
if (idx != -1) { if (idx != -1) {
final newBody = message.body; final newBody = Map<String, dynamic>.from(message.body);
newBody.remove('related_event'); newBody.remove('related_event');
messages[idx] = messages[idx].copyWith( messages[idx] = messages[idx].copyWith(
body: newBody, body: newBody,
updatedAt: message.updatedAt, updatedAt: message.updatedAt,
); );
if (message.relatedEventId != null) { }
await (_dt.db.snLocalChatMessage.update() if (message.relatedEventId != null) {
..where((e) => e.id.equals(message.relatedEventId!))) await (_dt.db.snLocalChatMessage.update()
.write( ..where((e) => e.id.equals(message.relatedEventId!)))
SnLocalChatMessageCompanion.custom( .write(
content: Constant(jsonEncode(messages[idx].toJson())), SnLocalChatMessageCompanion.custom(
), content: Constant(jsonEncode(messages[idx].toJson())),
); ),
} );
} }
} }
case 'messages.delete': case 'messages.delete':

View File

@ -333,6 +333,31 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
} }
} }
final Menu _appTrayMenu = Menu(
items: [
MenuItem(
key: 'version_label',
label: 'Solian',
disabled: true,
),
MenuItem.separator(),
MenuItem.checkbox(
checked: false,
key: 'mute_notification',
label: 'trayMenuMuteNotification'.tr(),
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
);
Future<void> _trayInitialization() async { Future<void> _trayInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
@ -344,32 +369,20 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
trayManager.addListener(this); trayManager.addListener(this);
await trayManager.setIcon(icon); await trayManager.setIcon(icon);
Menu menu = Menu( _appTrayMenu.items![0] = MenuItem(
items: [ key: 'version_label',
MenuItem( label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
key: 'version_label', disabled: true,
label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
disabled: true,
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
); );
await trayManager.setContextMenu(menu);
await trayManager.setContextMenu(_appTrayMenu);
} }
Future<void> _notifyInitialization() async { Future<void> _notifyInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
await localNotifier.setup( await localNotifier.setup(
appName: 'solian', appName: 'Solian',
shortcutPolicy: ShortcutPolicy.requireCreate, shortcutPolicy: ShortcutPolicy.requireCreate,
); );
} }
@ -424,12 +437,23 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
@override @override
void onTrayMenuItemClick(MenuItem menuItem) { void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) { switch (menuItem.key) {
case 'mute_notification':
final nty = context.read<NotificationProvider>();
nty.isMuted = !nty.isMuted;
_appTrayMenu.items![2].checked = nty.isMuted;
trayManager.setContextMenu(_appTrayMenu);
break;
case 'window_show': case 'window_show':
appWindow.show(); // To prevent the window from being hide after just show on macOS
Timer(const Duration(milliseconds: 100), () => appWindow.show());
break; break;
case 'exit': case 'exit':
_appLifecycleListener?.dispose(); _appLifecycleListener?.dispose();
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); if (Platform.isWindows) {
appWindow.close();
} else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
break; break;
} }
} }

View File

@ -79,6 +79,7 @@ class NotificationProvider extends ChangeNotifier {
List<SnNotification> notifications = List.empty(growable: true); List<SnNotification> notifications = List.empty(growable: true);
int? skippableNotifyChannel; int? skippableNotifyChannel;
bool isMuted = false;
void listen() { void listen() {
_ws.pk.stream.listen((event) { _ws.pk.stream.listen((event) {
@ -88,7 +89,8 @@ class NotificationProvider extends ChangeNotifier {
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true; final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact(); if (doHaptic) HapticFeedback.mediumImpact();
if (notification.topic == 'messaging.message') { if (notification.topic == 'messaging.message' &&
skippableNotifyChannel != null) {
if (notification.metadata['channel_id'] != null && if (notification.metadata['channel_id'] != null &&
notification.metadata['channel_id'] == skippableNotifyChannel) { notification.metadata['channel_id'] == skippableNotifyChannel) {
return; return;
@ -106,7 +108,7 @@ class NotificationProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
updateTray(); updateTray();
if (!kIsWeb) { if (!kIsWeb && !isMuted) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
LocalNotification notify = LocalNotification( LocalNotification notify = LocalNotification(
title: notification.title, title: notification.title,

View File

@ -41,7 +41,8 @@ class SnAttachmentProvider {
return out; return out;
} }
Future<List<SnAttachment?>> getMultiple(List<String> rids, {noCache = false}) async { Future<List<SnAttachment?>> getMultiple(List<String> rids,
{noCache = false}) async {
final result = List<SnAttachment?>.filled(rids.length, null); final result = List<SnAttachment?>.filled(rids.length, null);
final Map<String, int> randomMapping = {}; final Map<String, int> randomMapping = {};
for (int i = 0; i < rids.length; i++) { for (int i = 0; i < rids.length; i++) {
@ -62,8 +63,10 @@ class SnAttachmentProvider {
'id': pendingFetch.join(','), 'id': pendingFetch.join(','),
}, },
); );
final List<SnAttachment?> out = final List<SnAttachment?> out = resp.data['data']
resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).cast<SnAttachment?>().toList(); .map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e))
.cast<SnAttachment?>()
.toList();
for (final item in out) { for (final item in out) {
if (item == null) continue; if (item == null) continue;
@ -77,7 +80,13 @@ class SnAttachmentProvider {
return result; return result;
} }
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime', 'mp4': 'video/mp4'}; static Map<String, String> mimetypeOverrides = {
'mov': 'video/quicktime',
'mp4': 'video/mp4',
'm4a': 'audio/mp4',
'apng': 'image/apng',
'webp': 'image/webp',
};
Future<SnAttachment> directUploadOne( Future<SnAttachment> directUploadOne(
Uint8List data, Uint8List data,
@ -89,8 +98,11 @@ class SnAttachmentProvider {
bool analyzeNow = false, bool analyzeNow = false,
}) async { }) async {
final filePayload = MultipartFile.fromBytes(data, filename: filename); final filePayload = MultipartFile.fromBytes(data, filename: filename);
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename; final fileAlt = filename.contains('.')
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); ? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride; String? mimetypeOverride;
if (mimetype != null) { if (mimetype != null) {
@ -127,8 +139,11 @@ class SnAttachmentProvider {
Map<String, dynamic>? metadata, { Map<String, dynamic>? metadata, {
String? mimetype, String? mimetype,
}) async { }) async {
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename; final fileAlt = filename.contains('.')
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); ? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride; String? mimetypeOverride;
if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) { if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) {
@ -146,7 +161,10 @@ class SnAttachmentProvider {
if (mimetypeOverride != null) 'mimetype': mimetypeOverride, if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
}); });
return (SnAttachmentFragment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int); return (
SnAttachmentFragment.fromJson(resp.data['meta']),
resp.data['chunk_size'] as int
);
} }
Future<dynamic> _chunkedUploadOnePart( Future<dynamic> _chunkedUploadOnePart(
@ -197,7 +215,10 @@ class SnAttachmentProvider {
(entry.value + 1) * chunkSize, (entry.value + 1) * chunkSize,
await file.length(), await file.length(),
); );
final data = Uint8List.fromList(await file.openRead(beginCursor, endCursor).expand((chunk) => chunk).toList()); final data = Uint8List.fromList(await file
.openRead(beginCursor, endCursor)
.expand((chunk) => chunk)
.toList());
final result = await _chunkedUploadOnePart( final result = await _chunkedUploadOnePart(
data, data,

View File

@ -54,14 +54,20 @@ class AccountSettingsScreen extends StatelessWidget {
child: DropdownButton2<Locale?>( child: DropdownButton2<Locale?>(
isExpanded: true, isExpanded: true,
items: [ items: [
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) { ...EasyLocalization.of(context)!
.supportedLocales
.mapIndexed((idx, ele) {
return DropdownMenuItem<Locale?>( return DropdownMenuItem<Locale?>(
value: Locale.parse(ele.toString()), value: Locale.parse(ele.toString()),
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14), child: Text('${ele.languageCode}-${ele.countryCode}')
.fontSize(14),
); );
}), }),
], ],
value: ua.user?.language != null ? Locale.parse(ua.user!.language) : Locale.parse('en-US'), value: ua.user?.language != null
? (Locale.tryParse(ua.user!.language) ??
Locale.parse('en-US'))
: Locale.parse('en-US'),
onChanged: (Locale? value) { onChanged: (Locale? value) {
if (value == null) return; if (value == null) return;
_setAccountLanguage(context, value); _setAccountLanguage(context, value);

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart'; import 'package:responsive_framework/responsive_framework.dart';
@ -41,6 +42,7 @@ class _ChatScreenState extends State<ChatScreen> {
Future<void> _fetchWhatsNew() async { Future<void> _fetchWhatsNew() async {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/whats-new'); final resp = await sn.client.get('/cgi/im/whats-new');
if (resp.data == null) return;
final List<dynamic> out = resp.data; final List<dynamic> out = resp.data;
setState(() { setState(() {
_unreadCounts = {for (var v in out) v['channel_id']: v['count']}; _unreadCounts = {for (var v in out) v['channel_id']: v['count']};
@ -135,6 +137,28 @@ class _ChatScreenState extends State<ChatScreen> {
_fetchWhatsNew(); _fetchWhatsNew();
} }
void _onTapChannel(SnChannel channel) {
final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>(); final ud = context.read<UserDirectoryProvider>();
@ -284,23 +308,7 @@ class _ChatScreenState extends State<ChatScreen> {
?.avatar, ?.avatar,
), ),
onTap: () { onTap: () {
if (doExpand) { _onTapChannel(channel);
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
}, },
); );
} }
@ -318,10 +326,43 @@ class _ChatScreenState extends State<ChatScreen> {
], ],
), ),
subtitle: lastMessage != null subtitle: lastMessage != null
? Text( ? Row(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', children: [
maxLines: 1, Badge(
overflow: TextOverflow.ellipsis, label: Text(ud
.getAccountFromCache(
lastMessage.sender.accountId)
?.nick ??
'unknown'.tr()),
backgroundColor:
Theme.of(context).colorScheme.primary,
),
const Gap(6),
Expanded(
child: Text(
lastMessage.body['text'] ??
'Unable preview',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Text(
DateFormat(
lastMessage.createdAt.toLocal().day ==
DateTime.now().day
? 'HH:mm'
: lastMessage.createdAt
.toLocal()
.year ==
DateTime.now().year
? 'MM/dd'
: 'yy/MM/dd',
).format(lastMessage.createdAt.toLocal()),
style: GoogleFonts.robotoMono(
fontSize: 12,
),
),
],
) )
: Text( : Text(
channel.description, channel.description,
@ -331,7 +372,7 @@ class _ChatScreenState extends State<ChatScreen> {
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 16), const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage( leading: AccountImage(
content: null, content: channel.realm?.avatar,
fallbackWidget: const Icon(Symbols.chat, size: 20), fallbackWidget: const Icon(Symbols.chat, size: 20),
), ),
onTap: () { onTap: () {
@ -340,18 +381,7 @@ class _ChatScreenState extends State<ChatScreen> {
setState(() => _focusChannel = channel); setState(() => _focusChannel = channel);
return; return;
} }
GoRouter.of(context).pushNamed( _onTapChannel(channel);
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
}, },
); );
}, },

View File

@ -13,6 +13,7 @@ import 'package:surface/controllers/chat_message_controller.dart';
import 'package:surface/controllers/post_write_controller.dart'; import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/channel.dart'; import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart'; import 'package:surface/providers/chat_call.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
@ -84,6 +85,10 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
orElse: () => null, orElse: () => null,
); );
} }
if (!mounted) return;
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = _channel!.id;
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -232,6 +237,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
void dispose() { void dispose() {
_wsSubscription?.cancel(); _wsSubscription?.cancel();
_messageController.dispose(); _messageController.dispose();
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = null;
super.dispose(); super.dispose();
} }

View File

@ -2,7 +2,6 @@ import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -94,8 +93,12 @@ class _HomeScreenState extends State<HomeScreen> {
: MainAxisAlignment.start, : MainAxisAlignment.start,
children: [ children: [
_HomeDashUpdateWidget( _HomeDashUpdateWidget(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 8, left: 8, right: 8)), bottom: 8,
left: 8,
right: 8,
),
),
_HomeDashSpecialDayWidget().padding(horizontal: 8), _HomeDashSpecialDayWidget().padding(horizontal: 8),
StaggeredGrid.extent( StaggeredGrid.extent(
maxCrossAxisExtent: 280, maxCrossAxisExtent: 280,

View File

@ -161,7 +161,7 @@ class ChatMessage extends StatelessWidget {
if (data.preload?.quoteEvent != null) if (data.preload?.quoteEvent != null)
StyledWidget(Container( StyledWidget(Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: 480, maxWidth: 360,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
@ -210,9 +210,8 @@ class ChatMessage extends StatelessWidget {
AttachmentList( AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,
bordered: true, bordered: true,
maxHeight: 560, maxHeight: 360,
maxWidth: 480, maxWidth: 480 - 48 - padding.left,
minWidth: 480,
padding: padding.copyWith(top: 8, left: 48 + padding.left), padding: padding.copyWith(top: 8, left: 48 + padding.left),
), ),
if (!hasMerged && !isCompact) if (!hasMerged && !isCompact)
@ -292,14 +291,11 @@ class _ChatMessageText extends StatelessWidget {
buttonItems: items, buttonItems: items,
); );
}, },
child: Container( child: MarkdownTextContent(
constraints: const BoxConstraints(maxWidth: 480), content: data.body['text'],
child: MarkdownTextContent( isAutoWarp: true,
content: data.body['text'], isEnlargeSticker:
isAutoWarp: true, RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
),
), ),
), ),
if (data.updatedAt != data.createdAt) if (data.updatedAt != data.createdAt)

View File

@ -188,29 +188,19 @@ class AppRootScaffold extends StatelessWidget {
child: Text( child: Text(
'Solar Network', 'Solar Network',
style: GoogleFonts.spaceGrotesk(), style: GoogleFonts.spaceGrotesk(),
textAlign: !kIsWeb textAlign: Platform.isMacOS
? Platform.isMacOS ? TextAlign.center
? TextAlign.center : TextAlign.start,
: null
: null,
).padding(horizontal: 12, vertical: 5), ).padding(horizontal: 12, vertical: 5),
), ),
if (!Platform.isMacOS) if (!Platform.isMacOS)
Row( MinimizeWindowButton(colors: windowButtonColor),
mainAxisSize: MainAxisSize.min, if (!Platform.isMacOS)
children: [ MaximizeWindowButton(colors: windowButtonColor),
Expanded(child: MoveWindow()), if (!Platform.isMacOS)
Row( CloseWindowButton(
children: [ colors: windowButtonColor,
MinimizeWindowButton( onPressed: () => appWindow.hide(),
colors: windowButtonColor),
MaximizeWindowButton(
colors: windowButtonColor),
CloseWindowButton(
colors: windowButtonColor),
],
),
],
), ),
], ],
), ),
@ -226,16 +216,18 @@ class AppRootScaffold extends StatelessWidget {
child: NotifyIndicator()), child: NotifyIndicator()),
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
Positioned( Positioned(
bottom: safeBottom > 0 ? safeBottom : 16, bottom: safeBottom > 0 ? safeBottom : 16,
left: 0, left: 0,
right: 0, right: 0,
child: ConnectionIndicator()) child: ConnectionIndicator(),
)
else else
Positioned( Positioned(
top: safeTop > 0 ? safeTop : 16, top: safeTop > 0 ? safeTop : 16,
left: 0, left: 0,
right: 0, right: 0,
child: ConnectionIndicator()), child: ConnectionIndicator(),
),
], ],
), ),
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null, drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 2.3.2+73 version: 2.3.2+75
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4