Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
acbc125dec | |||
ad0ee971c1 | |||
52d6bb083e | |||
2027eab49b | |||
566ebde1dd | |||
9e039cc532 | |||
c4b95d7084 | |||
a66129a9ba | |||
44e1a8bf67 | |||
efcfd3f57d | |||
84759715a4 | |||
fda09382dd | |||
2c5dd0563a |
@ -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."
|
||||||
|
@ -717,6 +717,7 @@
|
|||||||
"stickersNewDescription": "创建一个新的贴图。",
|
"stickersNewDescription": "创建一个新的贴图。",
|
||||||
"stickersPackNew": "新建贴图包",
|
"stickersPackNew": "新建贴图包",
|
||||||
"trayMenuShow": "显示",
|
"trayMenuShow": "显示",
|
||||||
|
"trayMenuMuteNotification": "静音通知",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"forceUpdate": "强制更新",
|
"forceUpdate": "强制更新",
|
||||||
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。"
|
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。"
|
||||||
|
@ -717,6 +717,7 @@
|
|||||||
"stickersNewDescription": "創建一個新的貼圖。",
|
"stickersNewDescription": "創建一個新的貼圖。",
|
||||||
"stickersPackNew": "新建貼圖包",
|
"stickersPackNew": "新建貼圖包",
|
||||||
"trayMenuShow": "顯示",
|
"trayMenuShow": "顯示",
|
||||||
|
"trayMenuMuteNotification": "靜音通知",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"forceUpdate": "強制更新",
|
"forceUpdate": "強制更新",
|
||||||
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
|
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
|
||||||
|
@ -717,6 +717,7 @@
|
|||||||
"stickersNewDescription": "創建一個新的貼圖。",
|
"stickersNewDescription": "創建一個新的貼圖。",
|
||||||
"stickersPackNew": "新建貼圖包",
|
"stickersPackNew": "新建貼圖包",
|
||||||
"trayMenuShow": "顯示",
|
"trayMenuShow": "顯示",
|
||||||
|
"trayMenuMuteNotification": "靜音通知",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"forceUpdate": "強制更新",
|
"forceUpdate": "強制更新",
|
||||||
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
|
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
|
||||||
|
@ -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':
|
||||||
@ -322,6 +324,7 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
|
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
|
||||||
|
..where((e) => e.channelId.equals(channel!.id))
|
||||||
..limit(1)
|
..limit(1)
|
||||||
..orderBy([
|
..orderBy([
|
||||||
(e) =>
|
(e) =>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,10 +78,25 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
int showingTrayCount = 0;
|
int showingTrayCount = 0;
|
||||||
List<SnNotification> notifications = List.empty(growable: true);
|
List<SnNotification> notifications = List.empty(growable: true);
|
||||||
|
|
||||||
|
int? skippableNotifyChannel;
|
||||||
|
bool isMuted = false;
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
_ws.pk.stream.listen((event) {
|
_ws.pk.stream.listen((event) {
|
||||||
if (event.method == 'notifications.new') {
|
if (event.method == 'notifications.new') {
|
||||||
final notification = SnNotification.fromJson(event.payload!);
|
final notification = SnNotification.fromJson(event.payload!);
|
||||||
|
|
||||||
|
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||||
|
if (doHaptic) HapticFeedback.mediumImpact();
|
||||||
|
|
||||||
|
if (notification.topic == 'messaging.message' &&
|
||||||
|
skippableNotifyChannel != null) {
|
||||||
|
if (notification.metadata['channel_id'] != null &&
|
||||||
|
notification.metadata['channel_id'] == skippableNotifyChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showingCount < 0) showingCount = 0;
|
if (showingCount < 0) showingCount = 0;
|
||||||
showingCount++;
|
showingCount++;
|
||||||
showingTrayCount++;
|
showingTrayCount++;
|
||||||
@ -92,10 +107,8 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
updateTray();
|
updateTray();
|
||||||
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
|
||||||
if (doHaptic) HapticFeedback.mediumImpact();
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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>();
|
||||||
@ -258,7 +282,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
channel.name),
|
channel.name),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
if (_unreadCounts?[channel.id] != null)
|
if (_unreadCounts?[channel.id] != null &&
|
||||||
|
_unreadCounts![channel.id]! > 0)
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('${_unreadCounts![channel.id]}'),
|
label: Text('${_unreadCounts![channel.id]}'),
|
||||||
),
|
),
|
||||||
@ -283,19 +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) _refreshChannels(noRemote: true);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -305,17 +318,51 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(channel.name)),
|
Expanded(child: Text(channel.name)),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
if (_unreadCounts?[channel.id] != null)
|
if (_unreadCounts?[channel.id] != null &&
|
||||||
|
_unreadCounts![channel.id]! > 0)
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('${_unreadCounts![channel.id]}'),
|
label: Text('${_unreadCounts![channel.id]}'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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,
|
||||||
@ -325,23 +372,16 @@ 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: () {
|
||||||
if (doExpand) {
|
if (doExpand) {
|
||||||
|
_unreadCounts?[channel.id] = 0;
|
||||||
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 (value == true) _refreshChannels(noRemote: true);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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+72
|
version: 2.3.2+75
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Reference in New Issue
Block a user