Compare commits

..

17 Commits

Author SHA1 Message Date
997934f680 🚀 Launch 2.3.2+71 2025-02-23 14:51:15 +08:00
26e69d6264 Desktop local notification 2025-02-23 14:49:38 +08:00
153eabcbf2 💄 Enlarge emote when there is only one 2025-02-23 14:40:40 +08:00
6d0145c335 💄 Make attachment in chat aligned with message 2025-02-23 14:35:51 +08:00
81a79f9476 Stickers 2025-02-23 14:23:06 +08:00
537f404fe0 Delete sticker 2025-02-23 14:15:32 +08:00
eb29f76b9a Create new sticker to pack 2025-02-23 14:11:45 +08:00
56816dc060 More debug options in settings 2025-02-23 13:20:41 +08:00
899d5f3e5e Sticker page & add sticker 2025-02-23 13:14:16 +08:00
c8c455bb57 🐛 Make sure the send read event triggered before dispose chat message controller 2025-02-23 12:03:17 +08:00
5468fc0748 Two pane chat screen 2025-02-23 11:36:02 +08:00
78516abf2e Chat unread count 2025-02-23 01:49:07 +08:00
0424f98eb5 🐛 Fix title bar on macOS don't centered 2025-02-23 01:06:24 +08:00
2188b8b2e2 💄 Optimize (idk what i did) 2025-02-23 00:50:37 +08:00
0bf614a75c 🔀 Merge pull request '♻️ Use sqlite to replace hive' (#5) from refactor/sqlite into master
Reviewed-on: HyperNet/Surface#5
2025-02-22 12:49:51 +00:00
9f21f744a4 Remove Hive 2025-02-22 20:47:27 +08:00
b94cda6205 🗑️ Remove Hive related code 2025-02-22 20:46:47 +08:00
43 changed files with 2107 additions and 1297 deletions

View File

@@ -55,6 +55,7 @@ jobs:
sudo apt-get install libmpv-dev mpv
sudo apt-get install libayatana-appindicator3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install libnotify-dev
- run: flutter pub get
- run: flutter build linux
- name: Archive production artifacts

View File

@@ -687,5 +687,36 @@
"databaseSize": "Database Size",
"databaseDelete": "Delete Database",
"databaseDeleteDescription": "Remove the database on your local disk, the content will be fetched from server again.",
"databaseDeleted": "The local database has been deleted."
"databaseDeleted": "The local database has been deleted.",
"settingsEnablePushNotifications": "Enable Push Notifications",
"settingsEnablePushNotificationsDescription": "Re-enable and request permission to receive push notifications. Just in case it didn't run automatically.",
"settingsEnabledPushNotifications": "Push notification has been enabled.",
"screenStickers": "Stickers",
"stickersDiscovery": "Discovery",
"stickersOwned": "Owned",
"stickersCreated": "Created",
"stickersAdd": "Add Sticker Pack",
"stickersAdded": "Sticker pack has been added.",
"add": "Add",
"stickersRemoved": "Sticker pack has been removed, you can add it again anytime.",
"stickersReload": "Reload Stickers",
"stickersReloadDescription": "Reload stickers from the server, update the sticker picker.",
"stickersReloaded": "Sticker packs has been reloaded.",
"stickersPackDelete": "Delete Pack {}",
"stickersPackDeleteDescription": "Are you sure you want to delete this sticker pack? This operation is irreversible.",
"stickersPackDeleted": "Sticker pack has been deleted.",
"stickersDelete": "Delete Sticker {}",
"stickersDeleteDescription": "Are you sure you want to delete this sticker? This operation is irreversible.",
"stickersDeleted": "Sticker has been deleted.",
"fieldStickerName": "Sticker Name",
"fieldStickerAlias": "Sticker Alias",
"fieldStickerAliasHint": "The unique sticker placeholder with the pack prefix.",
"fieldStickerPackName": "Name",
"fieldStickerPackDescription": "Description",
"fieldStickerPackPrefix": "Prefix",
"fieldStickerAttachment": "Attachment",
"stickersNew": "New Sticker",
"stickersNewDescription": "Create a new sticker belongs to this pack.",
"stickersPackNew": "New Sticker Pack",
"trayMenuShow": "Show"
}

View File

@@ -685,5 +685,36 @@
"databaseSize": "数据库大小",
"databaseDelete": "删除数据库",
"databaseDeleteDescription": "删除本地数据库,内容将从服务器重新获取。",
"databaseDeleted": "本地数据库已被删除。"
"databaseDeleted": "本地数据库已被删除。",
"settingsEnablePushNotifications": "启用推送数据",
"settingsEnablePushNotificationsDescription": "重新启用并请求推送权限,以防自动激活失败。",
"settingsEnabledPushNotifications": "推送通知已经注册。",
"screenStickers": "贴图",
"stickersDiscovery": "发现",
"stickersOwned": "由我拥有",
"stickersCreated": "由我发布",
"stickersAdd": "添加贴图包",
"stickersAdded": "贴图包已添加。",
"add": "添加",
"stickersRemoved": "贴图包已被移除,你可以随时再次添加回来。",
"stickersReload": "重载贴图包",
"stickersReloadDescription": "从服务器重新加载添加过的贴图,更新贴图选择器。",
"stickersReloaded": "贴图包已重载。",
"stickersPackDelete": "删除贴图包 {}",
"stickersPackDeleteDescription": "你确定要删除这个贴图包吗?这个操作不可撤销。",
"stickersPackDeleted": "贴图包已被删除。",
"stickersDelete": "删除贴图 {}",
"stickersDeleteDescription": "你确定要删除这个贴图吗?这个操作不可撤销。",
"stickersDeleted": "贴图已被删除。",
"fieldStickerName": "贴图名称",
"fieldStickerAlias": "贴图别名",
"fieldStickerAliasHint": "和贴图包前缀组合成为本贴图的唯一占位符。",
"fieldStickerPackName": "名称",
"fieldStickerPackDescription": "描述",
"fieldStickerPackPrefix": "贴图包前缀",
"fieldStickerAttachment": "附件",
"stickersNew": "新建贴图",
"stickersNewDescription": "创建一个新的贴图。",
"stickersPackNew": "新建贴图包",
"trayMenuShow": "显示"
}

View File

@@ -685,5 +685,35 @@
"databaseSize": "數據庫大小",
"databaseDelete": "刪除數據庫",
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
"databaseDeleted": "本地數據庫已被刪除。"
"databaseDeleted": "本地數據庫已被刪除。",
"settingsEnablePushNotifications": "啓用推送數據",
"settingsEnablePushNotificationsDescription": "重新啓用並請求推送權限,以防自動激活失敗。",
"settingsEnabledPushNotifications": "推送通知已經註冊。",
"screenStickers": "貼圖",
"stickersDiscovery": "發現",
"stickersOwned": "由我擁有",
"stickersCreated": "由我發佈",
"stickersAdd": "添加貼圖包",
"stickersAdded": "貼圖包已添加。",
"add": "添加",
"stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。",
"stickersReload": "重載貼圖包",
"stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。",
"stickersReloaded": "貼圖包已重載。",
"stickersPackDelete": "刪除貼圖包 {}",
"stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。",
"stickersPackDeleted": "貼圖包已被刪除。",
"stickersDelete": "刪除貼圖 {}",
"stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。",
"stickersDeleted": "貼圖已被刪除。",
"fieldStickerName": "貼圖名稱",
"fieldStickerAlias": "貼圖別名",
"fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。",
"fieldStickerPackName": "名稱",
"fieldStickerPackDescription": "描述",
"fieldStickerPackPrefix": "貼圖包前綴",
"fieldStickerAttachment": "附件",
"stickersNew": "新建貼圖",
"stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包"
}

View File

@@ -685,5 +685,35 @@
"databaseSize": "數據庫大小",
"databaseDelete": "刪除數據庫",
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
"databaseDeleted": "本地數據庫已被刪除。"
"databaseDeleted": "本地數據庫已被刪除。",
"settingsEnablePushNotifications": "啟用推送數據",
"settingsEnablePushNotificationsDescription": "重新啟用並請求推送權限,以防自動激活失敗。",
"settingsEnabledPushNotifications": "推送通知已經註冊。",
"screenStickers": "貼圖",
"stickersDiscovery": "發現",
"stickersOwned": "由我擁有",
"stickersCreated": "由我發佈",
"stickersAdd": "添加貼圖包",
"stickersAdded": "貼圖包已添加。",
"add": "添加",
"stickersRemoved": "貼圖包已被移除,你可以隨時再次添加回來。",
"stickersReload": "重載貼圖包",
"stickersReloadDescription": "從服務器重新加載添加過的貼圖,更新貼圖選擇器。",
"stickersReloaded": "貼圖包已重載。",
"stickersPackDelete": "刪除貼圖包 {}",
"stickersPackDeleteDescription": "你確定要刪除這個貼圖包嗎?這個操作不可撤銷。",
"stickersPackDeleted": "貼圖包已被刪除。",
"stickersDelete": "刪除貼圖 {}",
"stickersDeleteDescription": "你確定要刪除這個貼圖嗎?這個操作不可撤銷。",
"stickersDeleted": "貼圖已被刪除。",
"fieldStickerName": "貼圖名稱",
"fieldStickerAlias": "貼圖別名",
"fieldStickerAliasHint": "和貼圖包前綴組合成為本貼圖的唯一佔位符。",
"fieldStickerPackName": "名稱",
"fieldStickerPackDescription": "描述",
"fieldStickerPackPrefix": "貼圖包前綴",
"fieldStickerAttachment": "附件",
"stickersNew": "新建貼圖",
"stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包"
}

View File

@@ -42,58 +42,58 @@ PODS:
- Flutter
- file_saver (0.0.1):
- Flutter
- Firebase/Analytics (11.7.0):
- Firebase/Analytics (11.8.0):
- Firebase/Core
- Firebase/Core (11.7.0):
- Firebase/Core (11.8.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 11.7.0)
- Firebase/CoreOnly (11.7.0):
- FirebaseCore (~> 11.7.0)
- Firebase/Messaging (11.7.0):
- FirebaseAnalytics (~> 11.8.0)
- Firebase/CoreOnly (11.8.0):
- FirebaseCore (~> 11.8.0)
- Firebase/Messaging (11.8.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.7.0)
- firebase_analytics (11.4.2):
- Firebase/Analytics (= 11.7.0)
- FirebaseMessaging (~> 11.8.0)
- firebase_analytics (11.4.3):
- Firebase/Analytics (= 11.8.0)
- firebase_core
- Flutter
- firebase_core (3.11.0):
- Firebase/CoreOnly (= 11.7.0)
- firebase_core (3.12.0):
- Firebase/CoreOnly (= 11.8.0)
- Flutter
- firebase_messaging (15.2.2):
- Firebase/Messaging (= 11.7.0)
- firebase_messaging (15.2.3):
- Firebase/Messaging (= 11.8.0)
- firebase_core
- Flutter
- FirebaseAnalytics (11.7.0):
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
- FirebaseCore (~> 11.7.0)
- FirebaseAnalytics (11.8.0):
- FirebaseAnalytics/AdIdSupport (= 11.8.0)
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseAnalytics/AdIdSupport (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.7.0)
- GoogleAppMeasurement (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.7.0):
- FirebaseCoreInternal (~> 11.7.0)
- FirebaseCore (11.8.1):
- FirebaseCoreInternal (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.7.0):
- FirebaseCoreInternal (11.8.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseInstallations (11.8.0):
- FirebaseCore (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseMessaging (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@@ -122,21 +122,21 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (11.7.0):
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
- GoogleAppMeasurement (11.8.0):
- GoogleAppMeasurement/AdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.7.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
- GoogleAppMeasurement/AdIdSupport (11.8.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
@@ -210,9 +210,9 @@ PODS:
- SAMKeychain (1.5.3)
- screen_brightness_ios (0.1.0):
- Flutter
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)
- SDWebImage (5.20.1):
- SDWebImage/Core (= 5.20.1)
- SDWebImage/Core (5.20.1)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@@ -221,16 +221,16 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.49.0):
- sqlite3/common (= 3.49.0)
- sqlite3/common (3.49.0)
- sqlite3/dbstatvtab (3.49.0):
- sqlite3 (3.49.1):
- sqlite3/common (= 3.49.1)
- sqlite3/common (3.49.1)
- sqlite3/dbstatvtab (3.49.1):
- sqlite3/common
- sqlite3/fts5 (3.49.0):
- sqlite3/fts5 (3.49.1):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.0):
- sqlite3/perf-threadsafe (3.49.1):
- sqlite3/common
- sqlite3/rtree (3.49.0):
- sqlite3/rtree (3.49.1):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
@@ -403,23 +403,23 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
firebase_analytics: 7236e6115c1b4e62c2270faa29c052a317e31107
firebase_core: aa979ae726f00b3ef4ccf59dfb96170af84efbd4
firebase_messaging: 3af84b6a90aeac4d7a67fbf4c43a91e7083bea1f
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_analytics: 7ec1166af61987fa968766eb11587c562a5650ee
firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682
firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
@@ -440,11 +440,11 @@ SPEC CHECKSUMS:
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImage: 33d0f23bddeb5d209ae959153883247be6703713
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: 4922312598b67e1825c6a6c821296dcbf6783046
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe

View File

@@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:math' as math;
import 'package:dio/dio.dart';
@@ -499,9 +500,45 @@ class ChatMessageController extends ChangeNotifier {
}
}
Timer? _readEventDebounce;
int? _readEventAnchor;
void readEvent(int id) {
if (_readEventAnchor != null) {
_readEventAnchor = math.max(_readEventAnchor!, id);
} else {
_readEventAnchor = id;
}
if (_readEventDebounce?.isActive ?? false) {
_readEventDebounce?.cancel();
}
_readEventDebounce = Timer(const Duration(milliseconds: 500), () {
_sendReadEvent();
});
}
void _sendReadEvent() {
_ws.conn?.sink.add(jsonEncode(
WebSocketPackage(
method: 'events.read',
endpoint: 'im',
payload: {
'channel_member_id': profile!.id,
'event_id': _readEventAnchor,
},
).toJson(),
));
log('[Messaging] Send read event request: $_readEventAnchor');
}
@override
void dispose() {
_wsSubscription?.cancel();
if (_readEventDebounce?.isActive ?? false) {
_sendReadEvent();
}
_readEventDebounce?.cancel();
super.dispose();
}
}

View File

@@ -13,7 +13,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
@@ -41,8 +40,6 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/websocket.dart';
import 'package:surface/providers/widget.dart';
import 'package:surface/router.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart';
import 'package:tray_manager/tray_manager.dart';
@@ -51,6 +48,7 @@ import 'package:workmanager/workmanager.dart';
import 'package:in_app_review/in_app_review.dart';
import 'package:image_picker_android/image_picker_android.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:local_notifier/local_notifier.dart';
@pragma('vm:entry-point')
void appBackgroundDispatcher() {
@@ -83,12 +81,6 @@ void main() async {
await EasyLocalization.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(SnChannelImplAdapter());
Hive.registerAdapter(SnRealmImplAdapter());
Hive.registerAdapter(SnChannelMemberImplAdapter());
Hive.registerAdapter(SnChatMessageImplAdapter());
if (!kIsWeb && !Platform.isLinux) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
@@ -115,7 +107,8 @@ void main() async {
}
if (!kIsWeb && Platform.isAndroid) {
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
final ImagePickerPlatform imagePickerImplementation =
ImagePickerPlatform.instance;
if (imagePickerImplementation is ImagePickerAndroid) {
imagePickerImplementation.useAndroidPhotoPicker = true;
}
@@ -234,7 +227,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
if (prefs.containsKey('first_boot_time')) {
final rawTime = prefs.getString('first_boot_time');
final time = DateTime.tryParse(rawTime ?? '');
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
if (time != null &&
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
final inAppReview = InAppReview.instance;
if (prefs.getBool('rating_requested') == true) return;
if (await inAppReview.isAvailable()) {
@@ -262,13 +256,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
).get(
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1',
);
final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
final remoteVersionString =
(resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
final localVersion = Version.parse(localVersionString.split('+').first);
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
final remoteBuildNumber =
int.tryParse(remoteVersionString.split('+').last) ?? 0;
final localBuildNumber =
int.tryParse(localVersionString.split('+').last) ?? 0;
log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
if ((remoteVersion > localVersion ||
remoteBuildNumber > localBuildNumber) &&
mounted) {
final config = context.read<ConfigProvider>();
config.setUpdate(remoteVersionString);
log("[Update] Update available: $remoteVersionString");
@@ -305,7 +304,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
await notify.registerPushNotifications();
if (!mounted) return;
final sticker = context.read<SnStickerProvider>();
await sticker.listStickerEagerly();
await sticker.listSticker();
log('[Bootstrap] Everything initialized!');
} catch (err) {
if (!mounted) return;
await context.showErrorDialog(err);
@@ -335,7 +335,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
Future<void> _trayInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
final icon = Platform.isWindows
? 'assets/icon/tray-icon.ico'
: 'assets/icon/tray-icon.png';
final appVersion = await PackageInfo.fromPlatform();
trayManager.addListener(this);
@@ -349,6 +351,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
disabled: true,
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
@@ -358,6 +364,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
await trayManager.setContextMenu(menu);
}
Future<void> _notifyInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
await localNotifier.setup(
appName: 'solian',
shortcutPolicy: ShortcutPolicy.requireCreate,
);
}
AppLifecycleListener? _appLifecycleListener;
@override
@@ -372,6 +387,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
_trayInitialization();
_hotkeyInitialization();
_notifyInitialization();
_initialize().then((_) {
_postInitialization();
_tryRequestRating();
@@ -407,6 +423,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
@override
void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) {
case 'window_show':
appWindow.show();
break;
case 'exit':
_appLifecycleListener?.dispose();
SystemChannels.platform.invokeMethod('SystemNavigator.pop');

View File

@@ -2,14 +2,13 @@ import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:surface/database/database.dart';
import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
class ChatChannelProvider extends ChangeNotifier {
static const kChatChannelBoxName = 'nex_chat_channels';
@@ -17,16 +16,13 @@ class ChatChannelProvider extends ChangeNotifier {
late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud;
late final DatabaseProvider _dt;
late final SnRealmProvider _rels;
ChatChannelProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>();
_dt = context.read<DatabaseProvider>();
_initializeLocalData();
}
Future<void> _initializeLocalData() async {
await Hive.openBox<SnChannel>(kChatChannelBoxName);
_rels = context.read<SnRealmProvider>();
}
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
@@ -50,16 +46,9 @@ class ChatChannelProvider extends ChangeNotifier {
}
Future<List<SnChannel>> _fetchChannelsFromServer({
String scope = 'global',
bool direct = false,
bool doNotSave = false,
}) async {
final resp = await _sn.client.get(
'/cgi/im/channels/$scope/me/available',
queryParameters: {
'direct': direct,
},
);
final resp = await _sn.client.get('/cgi/im/channels/me/available');
final out = List<SnChannel>.from(
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
);
@@ -74,7 +63,10 @@ class ChatChannelProvider extends ChangeNotifier {
final local = await (_dt.db.snLocalChatChannel.select()
..where((e) => e.alias.equals(key)))
.getSingleOrNull();
if (local != null) return local.content;
if (local != null) {
final out = local.content;
return out.copyWith(realm: await _rels.getRealm(out.realmId!));
}
var resp =
await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}');
@@ -82,8 +74,7 @@ class ChatChannelProvider extends ChangeNotifier {
// Preload realm of the channel
if (out.realmId != null) {
resp = await _sn.client.get('/cgi/id/realms/${out.realmId}');
out = out.copyWith(realm: SnRealm.fromJson(resp.data));
out = out.copyWith(realm: await _rels.getRealm(out.realmId!));
}
_saveChannelToLocal([out]);
@@ -104,44 +95,30 @@ class ChatChannelProvider extends ChangeNotifier {
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
]))
.get();
yield local.map((e) => e.content).toList();
final out = local.map((e) => e.content).toList();
for (var idx = 0; idx < out.length; idx++) {
final channel = out[idx];
if (channel.realmId != null) {
out[idx] = out[idx].copyWith(
realm: await _rels.getRealm(channel.realmId!),
);
}
}
yield out;
}
if (noRemote) return;
var resp = await _sn.client.get('/cgi/id/realms/me/available');
final realms = List<SnRealm>.from(
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
);
final realmMap = {
for (final realm in realms) realm.alias: realm,
};
final scopeToFetch = {'global', ...realms.map((e) => e.alias)};
final List<SnChannel> result = List.empty(growable: true);
final directMessages = await _fetchChannelsFromServer(
scope: scopeToFetch.first,
direct: true,
);
result.addAll(directMessages);
final nonBelongsChannels = await _fetchChannelsFromServer(
scope: scopeToFetch.first,
direct: false,
);
result.addAll(nonBelongsChannels);
for (final scope in scopeToFetch.skip(1)) {
final channel = await _fetchChannelsFromServer(
scope: scope,
direct: false,
doNotSave: true,
);
final out = channel.map((ele) => ele.copyWith(realm: realmMap[scope]));
_saveChannelToLocal(out);
result.addAll(out);
final channels = await _fetchChannelsFromServer();
for (var idx = 0; idx < channels.length; idx++) {
final channel = channels[idx];
if (channel.realmId != null) {
channels[idx] = channels[idx].copyWith(
realm: await _rels.getRealm(channel.realmId!),
);
}
}
result.addAll(channels);
yield result;
}

View File

@@ -63,6 +63,11 @@ class NavigationProvider extends ChangeNotifier {
screen: 'news',
label: 'screenNews',
),
AppNavDestination(
icon: Icon(Symbols.emoji_emotions, weight: 400, opticalSize: 20),
screen: 'stickers',
label: 'screenStickers',
),
AppNavDestination(
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
screen: 'album',
@@ -88,7 +93,8 @@ class NavigationProvider extends ChangeNotifier {
List<AppNavDestination> destinations = [];
int get pinnedDestinationCount => destinations.where((ele) => ele.isPinned).length;
int get pinnedDestinationCount =>
destinations.where((ele) => ele.isPinned).length;
NavigationProvider() {
buildDestinations(kDefaultPinnedDestination);
@@ -117,13 +123,17 @@ class NavigationProvider extends ChangeNotifier {
}
bool isIndexInRange(int min, int max) {
return _currentIndex != null && _currentIndex! >= min && _currentIndex! < max;
return _currentIndex != null &&
_currentIndex! >= min &&
_currentIndex! < max;
}
void autoDetectIndex(GoRouter? state) {
if (state == null) return;
final idx = destinations.indexWhere(
(ele) => ele.screen == state.routerDelegate.currentConfiguration.last.route.name,
(ele) =>
ele.screen ==
state.routerDelegate.currentConfiguration.last.route.name,
);
_currentIndex = idx == -1 ? null : idx;
notifyListeners();

View File

@@ -1,11 +1,13 @@
import 'dart:developer';
import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart';
@@ -84,7 +86,7 @@ class NotificationProvider extends ChangeNotifier {
showingCount++;
showingTrayCount++;
notifications.add(notification);
Future.delayed(const Duration(seconds: 3), () {
Future.delayed(const Duration(seconds: 5), () {
if (showingCount >= 0) showingCount--;
notifyListeners();
});
@@ -92,6 +94,20 @@ class NotificationProvider extends ChangeNotifier {
updateTray();
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (!kIsWeb) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
LocalNotification notify = LocalNotification(
title: notification.title,
subtitle: notification.subtitle,
body: notification.body,
);
notify.onClick = () {
appWindow.show();
};
notify.show();
}
}
}
});
}

View File

@@ -11,7 +11,8 @@ class SnStickerProvider {
final Map<int, List<SnSticker>> stickersByPack = {};
List<SnSticker> get stickers => _cache.values.where((ele) => ele != null).cast<SnSticker>().toList();
List<SnSticker> get stickers =>
_cache.values.where((ele) => ele != null).cast<SnSticker>().toList();
SnStickerProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
@@ -23,8 +24,18 @@ class SnStickerProvider {
void _cacheSticker(SnSticker sticker) {
_cache['${sticker.pack.prefix}:${sticker.alias}'] = sticker;
if (stickersByPack[sticker.pack.id] == null) stickersByPack[sticker.pack.id] = List.empty(growable: true);
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) stickersByPack[sticker.pack.id]!.add(sticker);
if (stickersByPack[sticker.pack.id] == null) {
stickersByPack[sticker.pack.id] = List.empty(growable: true);
}
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) {
stickersByPack[sticker.pack.id]!.add(sticker);
}
}
void putSticker(Iterable<SnSticker> sticker) {
for (final ele in sticker) {
_cacheSticker(ele);
}
}
Future<SnSticker?> lookupSticker(String alias) async {
@@ -46,26 +57,14 @@ class SnStickerProvider {
return null;
}
Future<void> listStickerEagerly() async {
var count = await listSticker();
for (var page = 1; count > 0; count -= 10) {
await listSticker(page: page);
page++;
}
}
Future<int> listSticker({int page = 0}) async {
Future<void> listSticker() async {
try {
final resp = await _sn.client.get('/cgi/uc/stickers', queryParameters: {
'take': 10,
'offset': page * 10,
});
final resp = await _sn.client.get('/cgi/uc/stickers');
final data = resp.data;
final stickers = List.from(data['data']).map((ele) => SnSticker.fromJson(ele));
final stickers = List.from(data).map((ele) => SnSticker.fromJson(ele));
for (final sticker in stickers) {
_cacheSticker(sticker);
}
return data['count'] as int;
} catch (err) {
log('[Sticker] Failed to list stickers: $err');
rethrow;

View File

@@ -34,13 +34,15 @@ import 'package:surface/screens/realm/realm_detail.dart';
import 'package:surface/screens/realm/realm_discovery.dart';
import 'package:surface/screens/settings.dart';
import 'package:surface/screens/sharing.dart';
import 'package:surface/screens/stickers.dart';
import 'package:surface/screens/stickers/pack_detail.dart';
import 'package:surface/screens/wallet.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/about.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
Widget _fadeThroughTransition(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
Widget _fadeThroughTransition(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
@@ -82,13 +84,15 @@ final _appRoutes = [
name: 'postSearch',
builder: (context, state) => PostSearchScreen(
initialTags: state.uri.queryParameters['tags']?.split(','),
initialCategories: state.uri.queryParameters['categories']?.split(','),
initialCategories:
state.uri.queryParameters['categories']?.split(','),
),
),
GoRoute(
path: '/publishers/:name',
name: 'postPublisher',
builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!),
builder: (context, state) =>
PostPublisherScreen(name: state.pathParameters['name']!),
),
GoRoute(
path: '/:slug',
@@ -100,52 +104,56 @@ final _appRoutes = [
),
],
),
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [
GoRoute(
path: '/wallet',
name: 'accountWallet',
builder: (context, state) => const WalletScreen(),
),
GoRoute(
path: '/settings',
name: 'accountSettings',
builder: (context, state) => AccountSettingsScreen(),
),
GoRoute(
path: '/settings/factors',
name: 'factorSettings',
builder: (context, state) => FactorSettingsScreen(),
),
GoRoute(
path: '/profile/edit',
name: 'accountProfileEdit',
builder: (context, state) => ProfileEditScreen(),
),
GoRoute(
path: '/publishers',
name: 'accountPublishers',
builder: (context, state) => PublisherScreen(),
),
GoRoute(
path: '/publishers/new',
name: 'accountPublisherNew',
builder: (context, state) => AccountPublisherNewScreen(),
),
GoRoute(
path: '/publishers/edit/:name',
name: 'accountPublisherEdit',
builder: (context, state) => AccountPublisherEditScreen(
name: state.pathParameters['name']!,
),
),
GoRoute(
path: '/:name',
name: 'accountProfilePage',
pageBuilder: (context, state) => NoTransitionPage(
child: UserScreen(name: state.pathParameters['name']!),
),
),
]),
GoRoute(
path: '/account',
name: 'account',
builder: (context, state) => const AccountScreen(),
routes: [
GoRoute(
path: '/wallet',
name: 'accountWallet',
builder: (context, state) => const WalletScreen(),
),
GoRoute(
path: '/settings',
name: 'accountSettings',
builder: (context, state) => AccountSettingsScreen(),
),
GoRoute(
path: '/settings/factors',
name: 'factorSettings',
builder: (context, state) => FactorSettingsScreen(),
),
GoRoute(
path: '/profile/edit',
name: 'accountProfileEdit',
builder: (context, state) => ProfileEditScreen(),
),
GoRoute(
path: '/publishers',
name: 'accountPublishers',
builder: (context, state) => PublisherScreen(),
),
GoRoute(
path: '/publishers/new',
name: 'accountPublisherNew',
builder: (context, state) => AccountPublisherNewScreen(),
),
GoRoute(
path: '/publishers/edit/:name',
name: 'accountPublisherEdit',
builder: (context, state) => AccountPublisherEditScreen(
name: state.pathParameters['name']!,
),
),
GoRoute(
path: '/:name',
name: 'accountProfilePage',
pageBuilder: (context, state) => NoTransitionPage(
child: UserScreen(name: state.pathParameters['name']!),
),
),
]),
GoRoute(
path: '/chat',
name: 'chat',
@@ -208,19 +216,39 @@ final _appRoutes = [
GoRoute(
path: '/:alias',
name: 'realmDetail',
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
builder: (context, state) =>
RealmDetailScreen(alias: state.pathParameters['alias']!),
),
],
),
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
GoRoute(
path: '/:hash',
name: 'newsDetail',
builder: (context, state) => NewsDetailScreen(
hash: state.pathParameters['hash']!,
GoRoute(
path: '/news',
name: 'news',
builder: (context, state) => const NewsScreen(),
routes: [
GoRoute(
path: '/:hash',
name: 'newsDetail',
builder: (context, state) => NewsDetailScreen(
hash: state.pathParameters['hash']!,
),
),
),
]),
],
),
GoRoute(
path: '/stickers',
name: 'stickers',
builder: (context, state) => const StickerScreen(),
routes: [
GoRoute(
path: '/packs/:id',
name: 'stickerPack',
builder: (context, state) => StickerPackScreen(
id: int.tryParse(state.pathParameters['id']!)!,
),
),
],
),
GoRoute(
path: '/album',
name: 'album',

View File

@@ -4,10 +4,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/websocket.dart';
@@ -45,7 +45,8 @@ class AccountScreen extends StatelessWidget {
? Stack(
fit: StackFit.expand,
children: [
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner),
fit: BoxFit.cover),
Positioned(
top: 0,
left: 0,
@@ -79,7 +80,9 @@ class AccountScreen extends StatelessWidget {
],
),
body: SingleChildScrollView(
child: ua.isAuthorized ? _AuthorizedAccountScreen() : _UnauthorizedAccountScreen(),
child: ua.isAuthorized
? _AuthorizedAccountScreen()
: _UnauthorizedAccountScreen(),
),
);
}
@@ -115,12 +118,15 @@ class _AuthorizedAccountScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(ua.user!.nick).textStyle(Theme.of(context).textTheme.titleLarge!),
Text(ua.user!.nick)
.textStyle(Theme.of(context).textTheme.titleLarge!),
const Gap(4),
Text('@${ua.user!.name}').textStyle(Theme.of(context).textTheme.bodySmall!),
Text('@${ua.user!.name}')
.textStyle(Theme.of(context).textTheme.bodySmall!),
],
),
Text(ua.user!.description).textStyle(Theme.of(context).textTheme.bodyMedium!),
Text(ua.user!.description)
.textStyle(Theme.of(context).textTheme.bodyMedium!),
],
),
);
@@ -193,8 +199,7 @@ class _AuthorizedAccountScreen extends StatelessWidget {
ua.logoutUser();
final ws = context.read<WebSocketProvider>();
ws.disconnect();
await Hive.deleteFromDisk();
await Hive.initFlutter();
context.read<DatabaseProvider>().removeDatabase();
},
),
],
@@ -220,7 +225,9 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
child: Icon(Symbols.waving_hand, size: 28),
),
const Gap(8),
Text('accountIntroTitle').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
Text('accountIntroTitle')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
Text('accountIntroSubtitle').tr(),
],
).padding(all: 20),

View File

@@ -5,16 +5,19 @@ import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:surface/providers/channel.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/screens/chat/room.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_select.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/unauthorized_hint.dart';
import 'package:uuid/uuid.dart';
@@ -33,6 +36,16 @@ class _ChatScreenState extends State<ChatScreen> {
List<SnChannel>? _channels;
Map<int, SnChatMessage>? _lastMessages;
Map<int, int>? _unreadCounts;
Future<void> _fetchWhatsNew() async {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/whats-new');
final List<dynamic> out = resp.data;
setState(() {
_unreadCounts = {for (var v in out) v['channel_id']: v['count']};
});
}
void _refreshChannels({bool noRemote = false}) {
final ua = context.read<UserProvider>();
@@ -113,10 +126,13 @@ class _ChatScreenState extends State<ChatScreen> {
}
}
SnChannel? _focusChannel;
@override
void initState() {
super.initState();
_refreshChannels();
_fetchWhatsNew();
}
@override
@@ -136,7 +152,10 @@ class _ChatScreenState extends State<ChatScreen> {
);
}
return AppScaffold(
final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
final chatList = AppScaffold(
noBackground: doExpand,
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenChat').tr(),
@@ -211,7 +230,10 @@ class _ChatScreenState extends State<ChatScreen> {
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: () => Future.sync(() => _refreshChannels()),
onRefresh: () => Future.wait([
Future.sync(() => _refreshChannels()),
_fetchWhatsNew(),
]),
child: ListView.builder(
itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) {
@@ -226,10 +248,22 @@ class _ChatScreenState extends State<ChatScreen> {
);
return ListTile(
title: Text(ud
.getAccountFromCache(otherMember?.accountId)
?.nick ??
channel.name),
title: Row(
children: [
Expanded(
child: Text(ud
.getAccountFromCache(
otherMember?.accountId)
?.nick ??
channel.name),
),
const Gap(8),
if (_unreadCounts?[channel.id] != null)
Badge(
label: Text('${_unreadCounts![channel.id]}'),
),
],
),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@@ -237,9 +271,7 @@ class _ChatScreenState extends State<ChatScreen> {
overflow: TextOverflow.ellipsis,
)
: Text(
'channelDirectMessageDescription'.tr(args: [
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
channel.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -251,6 +283,10 @@ class _ChatScreenState extends State<ChatScreen> {
?.avatar,
),
onTap: () {
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
@@ -265,7 +301,16 @@ class _ChatScreenState extends State<ChatScreen> {
}
return ListTile(
title: Text(channel.name),
title: Row(
children: [
Expanded(child: Text(channel.name)),
const Gap(8),
if (_unreadCounts?[channel.id] != null)
Badge(
label: Text('${_unreadCounts![channel.id]}'),
),
],
),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@@ -284,6 +329,10 @@ class _ChatScreenState extends State<ChatScreen> {
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
onTap: () {
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
@@ -291,7 +340,7 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias,
},
).then((value) {
if (value == true) _refreshChannels();
if (value == true) _refreshChannels(noRemote: true);
});
},
);
@@ -303,5 +352,27 @@ class _ChatScreenState extends State<ChatScreen> {
],
),
);
if (doExpand) {
return AppBackground(
isRoot: true,
child: Row(
children: [
SizedBox(width: 340, child: chatList),
const VerticalDivider(width: 1),
if (_focusChannel != null)
Expanded(
child: ChatRoomScreen(
key: ValueKey(_focusChannel!.id),
scope: _focusChannel!.realm?.alias ?? 'global',
alias: _focusChannel!.alias,
),
),
],
),
);
}
return chatList;
}
}

View File

@@ -39,7 +39,8 @@ class ChatRoomScreen extends StatefulWidget {
final String alias;
final ChatRoomScreenExtra? extra;
const ChatRoomScreen({super.key, required this.scope, required this.alias, this.extra});
const ChatRoomScreen(
{super.key, required this.scope, required this.alias, this.extra});
@override
State<ChatRoomScreen> createState() => _ChatRoomScreenState();
@@ -192,10 +193,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) {
log('[ChatInput] Setting initial text and attachments...');
if (widget.extra!.initialText != null) {
_inputGlobalKey.currentState?.setInitialText(widget.extra!.initialText!);
_inputGlobalKey.currentState
?.setInitialText(widget.extra!.initialText!);
}
if (widget.extra!.initialAttachments != null) {
_inputGlobalKey.currentState?.setInitialAttachments(widget.extra!.initialAttachments!);
_inputGlobalKey.currentState
?.setInitialAttachments(widget.extra!.initialAttachments!);
}
});
}
@@ -241,12 +244,15 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
appBar: AppBar(
title: Text(
_channel?.type == 1
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? _channel!.name
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ??
_channel!.name
: _channel?.name ?? 'loading'.tr(),
),
actions: [
IconButton(
icon: _ongoingCall == null ? const Icon(Symbols.call) : const Icon(Symbols.call_end),
icon: _ongoingCall == null
? const Icon(Symbols.call)
: const Icon(Symbols.call_end),
onPressed: _isCalling
? null
: _ongoingCall == null
@@ -296,9 +302,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
)
],
),
)
.height(_ongoingCall != null ? 54 : 0, animate: true)
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
).height(_ongoingCall != null ? 54 : 0, animate: true).animate(
const Duration(milliseconds: 300),
Curves.fastLinearToSlowEaseIn),
if (_messageController.isPending)
Expanded(
child: const CircularProgressIndicator().center(),
@@ -316,6 +322,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
},
itemBuilder: (context, idx) {
final message = _messageController.messages[idx];
_messageController.readEvent(message.id);
bool canMerge = false, canMergePrevious = false;
if (idx > 0) {
@@ -337,7 +344,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
data: message,
isMerged: canMerge,
hasMerged: canMergePrevious,
isPending: _messageController.unconfirmedMessages.contains(message.uuid),
isPending: _messageController.unconfirmedMessages
.contains(message.uuid),
onReply: (value) {
_inputGlobalKey.currentState?.setReply(value);
},

View File

@@ -43,8 +43,10 @@ class ExploreScreen extends StatefulWidget {
// Cuz the global key make the selected category not update to child widget when the category is changed.
SnPostCategory? _selectedCategory;
class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProviderStateMixin {
late final TabController _tabController = TabController(length: 4, vsync: this);
class _ExploreScreenState extends State<ExploreScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController =
TabController(length: 4, vsync: this);
final _fabKey = GlobalKey<ExpandableFabState>();
final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>());
@@ -57,7 +59,10 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/categories?take=100');
setState(() {
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
_categories.addAll(resp.data
.map((e) => SnPostCategory.fromJson(e))
.cast<SnPostCategory>() ??
[]);
});
} catch (err) {
if (mounted) context.showErrorDialog(err);
@@ -94,20 +99,27 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
type: ExpandableFabType.up,
childrenAnimation: ExpandableFabAnimation.none,
overlayStyle: ExpandableFabOverlayStyle(
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
color: Theme.of(context)
.colorScheme
.surface
.withAlpha((255 * 0.5).round()),
),
openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(Symbols.add, size: 28),
fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
foregroundColor:
Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
),
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28),
fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
foregroundColor:
Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
),
children: [
@@ -241,13 +253,18 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.globe, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
Icon(Symbols.globe,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelGlobal',
maxLines: 1,
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
@@ -257,14 +274,19 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.group, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
Icon(Symbols.group,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelFriends',
maxLines: 1,
textAlign: TextAlign.center,
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
@@ -274,13 +296,18 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.subscriptions, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
Icon(Symbols.subscriptions,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelFollowing',
maxLines: 1,
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
@@ -290,13 +317,18 @@ class _ExploreScreenState extends State<ExploreScreen> with SingleTickerProvider
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.workspaces, size: 20, color: Theme.of(context).appBarTheme.foregroundColor),
Icon(Symbols.workspaces,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelRealm',
maxLines: 1,
).tr().textColor(Theme.of(context).appBarTheme.foregroundColor),
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
@@ -341,7 +373,11 @@ class _PostListWidget extends StatefulWidget {
final bool withRealm;
final Function onClearFilter;
const _PostListWidget({super.key, this.channel, this.withRealm = false, required this.onClearFilter});
const _PostListWidget(
{super.key,
this.channel,
this.withRealm = false,
required this.onClearFilter});
@override
State<_PostListWidget> createState() => _PostListWidgetState();
@@ -420,11 +456,13 @@ class _PostListWidgetState extends State<_PostListWidget> {
content: Text(
'postFilterWithCategory'.tr(args: [
'postCategory${_selectedCategory!.alias.capitalize()}'.trExists()
? 'postCategory${_selectedCategory!.alias.capitalize()}'.tr()
? 'postCategory${_selectedCategory!.alias.capitalize()}'
.tr()
: _selectedCategory!.name,
]),
),
leading: Icon(kCategoryIcons[_selectedCategory!.alias] ?? Symbols.question_mark),
leading: Icon(kCategoryIcons[_selectedCategory!.alias] ??
Symbols.question_mark),
actions: [
IconButton(
icon: const Icon(Symbols.clear),
@@ -486,7 +524,8 @@ class _PostListWidgetState extends State<_PostListWidget> {
itemCount: _posts.length,
isLoading: _isBusy,
centerLoading: true,
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
hasReachedMax:
_postCount != null && _posts.length >= _postCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
return OpenablePostItem(
@@ -526,7 +565,9 @@ class _PostCategoryPickerPopup extends StatelessWidget {
children: [
const Icon(Symbols.category, size: 24),
const Gap(16),
Text('postCategory').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
Text('postCategory')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
ListTile(
@@ -539,40 +580,46 @@ class _PostCategoryPickerPopup extends StatelessWidget {
},
),
const Divider(height: 1),
GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1,
children: categories
.map(
(ele) => InkWell(
onTap: () {
_selectedCategory = ele;
Navigator.pop(context, ele);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
kCategoryIcons[ele.alias] ?? Symbols.question_mark,
color: selected == ele ? Theme.of(context).colorScheme.primary : null,
),
const Gap(4),
Text(
'postCategory${ele.alias.capitalize()}'.trExists()
? 'postCategory${ele.alias.capitalize()}'.tr()
: ele.name,
)
.textStyle(Theme.of(context).textTheme.titleMedium!)
.textColor(selected == ele ? Theme.of(context).colorScheme.primary : null),
],
Expanded(
child: GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 1,
children: categories
.map(
(ele) => InkWell(
onTap: () {
_selectedCategory = ele;
Navigator.pop(context, ele);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
kCategoryIcons[ele.alias] ?? Symbols.question_mark,
color: selected == ele
? Theme.of(context).colorScheme.primary
: null,
),
const Gap(4),
Text(
'postCategory${ele.alias.capitalize()}'.trExists()
? 'postCategory${ele.alias.capitalize()}'.tr()
: ele.name,
)
.textStyle(Theme.of(context).textTheme.titleMedium!)
.textColor(selected == ele
? Theme.of(context).colorScheme.primary
: null),
],
),
),
),
)
.toList(),
)
.toList(),
),
),
],
);

View File

@@ -17,7 +17,9 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/database.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_sticker.dart';
import 'package:surface/providers/theme.dart';
import 'package:surface/theme.dart';
import 'package:surface/widgets/dialog.dart';
@@ -562,6 +564,46 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() {});
},
),
ListTile(
leading: const Icon(Symbols.notifications),
title: Text('settingsEnablePushNotifications').tr(),
subtitle:
Text('settingsEnablePushNotificationsDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
final nty = context.read<NotificationProvider>();
try {
await nty.registerPushNotifications();
if (!context.mounted) return;
HapticFeedback.heavyImpact();
context.showSnackbar(
'settingsEnabledPushNotifications'.tr());
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
},
),
ListTile(
leading: const Icon(Symbols.refresh),
title: Text('stickersReload').tr(),
subtitle: Text('stickersReloadDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
final stickers = context.read<SnStickerProvider>();
try {
await stickers.listSticker();
if (!context.mounted) return;
HapticFeedback.heavyImpact();
context.showSnackbar('stickersReloaded'.tr());
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
}
},
),
ListTile(
title: Text('settingsMiscAbout').tr(),
subtitle: Text('settingsMiscAboutDescription').tr(),

464
lib/screens/stickers.dart Normal file
View File

@@ -0,0 +1,464 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_sticker.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class StickerScreen extends StatefulWidget {
const StickerScreen({super.key});
@override
State<StickerScreen> createState() => _StickerScreenState();
}
class _StickerScreenState extends State<StickerScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController =
TabController(length: 3, vsync: this);
bool _isBusy = false;
int? _totalCount;
final List<SnStickerPack> _packs = List.empty(growable: true);
Future<void> _fetchPacks() async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final ua = context.read<UserProvider>();
final resp = await sn.client.get(
_tabController.index == 1
? '/cgi/uc/stickers/packs/own'
: '/cgi/uc/stickers/packs',
queryParameters: {
'take': 10,
'offset': _packs.length,
if (_tabController.index == 2) 'author': ua.user?.id,
},
);
if (resp.data is Map<String, dynamic>) {
_totalCount = resp.data['count'] as int?;
final out = List<SnStickerPack>.from(
resp.data['data'].map((ele) => SnStickerPack.fromJson(ele)),
);
_packs.addAll(out);
} else {
_totalCount = 0;
final out = List<SnStickerPack>.from(
resp.data.map((ele) => SnStickerPack.fromJson(ele)),
);
_packs.addAll(out);
}
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _removePack(SnStickerPack pack) async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}/own');
if (!mounted) return;
context.showSnackbar('stickersRemoved'.tr());
_refreshPacks();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _deletePack(SnStickerPack pack) async {
final confirm = await context.showConfirmDialog(
'stickersPackDelete'.tr(args: [pack.name]),
'stickersPackDeleteDescription'.tr(),
);
if (!confirm) return;
if (!mounted) return;
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/uc/stickers/packs/${pack.id}');
if (!mounted) return;
context.showSnackbar('stickersDeleted'.tr());
_refreshPacks();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _refreshPacks() async {
_packs.clear();
_totalCount = null;
await _fetchPacks();
}
@override
void initState() {
super.initState();
_fetchPacks();
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
_refreshPacks();
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
leading: AutoAppBarLeading(),
title: Text('screenStickers').tr(),
actions: [
IconButton(
icon: const Icon(Symbols.add_circle),
onPressed: () {
showDialog(
context: context,
builder: (context) => _StickerPackCreateDialog(),
).then((value) {
if (value == true) _refreshPacks();
});
},
),
const Gap(8),
],
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(
child: Text('stickersDiscovery'.tr()).textColor(
Theme.of(context).appBarTheme.foregroundColor,
),
),
Tab(
child: Text('stickersOwned'.tr()).textColor(
Theme.of(context).appBarTheme.foregroundColor,
),
),
Tab(
child: Text('stickersCreated'.tr()).textColor(
Theme.of(context).appBarTheme.foregroundColor,
),
),
],
),
),
body: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: _refreshPacks,
child: InfiniteList(
itemCount: _packs.length,
onFetchData: _fetchPacks,
hasReachedMax: _totalCount != null && _packs.length >= _totalCount!,
isLoading: _isBusy,
itemBuilder: (context, idx) {
final pack = _packs[idx];
return ListTile(
title: Text(pack.name),
subtitle: Text(
pack.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
trailing: _tabController.index == 1
? IconButton(
onPressed: () {
_removePack(pack);
},
icon: const Icon(Symbols.remove),
)
: _tabController.index == 2
? IconButton(
onPressed: () {
_deletePack(pack);
},
icon: const Icon(Symbols.delete),
)
: null,
onTap: () {
if (_tabController.index == 0) {
showModalBottomSheet(
context: context,
builder: (context) => _StickerPackAddPopup(pack: pack),
).then((value) {
if (value == true && _tabController.index == 1) {
_refreshPacks();
}
});
} else {
GoRouter.of(context).pushNamed(
'stickerPack',
pathParameters: {
'id': pack.id.toString(),
},
);
}
},
);
},
),
),
),
);
}
}
class _StickerPackAddPopup extends StatefulWidget {
final SnStickerPack pack;
const _StickerPackAddPopup({required this.pack});
@override
State<_StickerPackAddPopup> createState() => _StickerPackAddPopupState();
}
class _StickerPackAddPopupState extends State<_StickerPackAddPopup> {
SnStickerPack? _pack;
bool _isBusy = false;
Future<void> _fetchPack() async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final resp =
await sn.client.get('/cgi/uc/stickers/packs/${widget.pack.id}');
_pack = SnStickerPack.fromJson(resp.data);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchPack();
}
bool _isAdding = false;
Future<void> _addPack() async {
if (_pack == null) return;
try {
setState(() => _isAdding = true);
final sn = context.read<SnNetworkProvider>();
final stickers = context.read<SnStickerProvider>();
await sn.client.post(
'/cgi/uc/stickers/packs/${widget.pack.id}/own',
);
if (!mounted) return;
context.showSnackbar('stickersAdded'.tr());
if (_pack?.stickers != null) stickers.putSticker(_pack!.stickers!);
Navigator.pop(context, true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isAdding = false);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.add, size: 24),
const Gap(16),
Text('stickersAdd', style: Theme.of(context).textTheme.titleLarge)
.tr(),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.pack.name).bold(),
Text(
widget.pack.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
ElevatedButton(
onPressed: _isAdding ? null : _addPack,
child: Text('add').tr(),
),
],
).padding(horizontal: 24),
LoadingIndicator(isActive: _isBusy),
if (_pack?.stickers != null)
Expanded(
child: GridView.extent(
padding: EdgeInsets.only(left: 20, right: 20, top: 8),
maxCrossAxisExtent: 48,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: _pack!.stickers!
.map(
(ele) => ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color:
Theme.of(context).colorScheme.surfaceContainerHigh,
child: AttachmentItem(
data: ele.attachment,
heroTag: 'sticker-pack-${ele.attachment.rid}',
fit: BoxFit.contain,
),
),
),
)
.toList(),
),
),
],
);
}
}
class _StickerPackCreateDialog extends StatefulWidget {
const _StickerPackCreateDialog();
@override
State<_StickerPackCreateDialog> createState() =>
_StickerPackCreateDialogState();
}
class _StickerPackCreateDialogState extends State<_StickerPackCreateDialog> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _prefixController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
bool _isBusy = false;
Future<void> _createPack() async {
if (_nameController.text.isEmpty ||
_prefixController.text.isEmpty ||
_descriptionController.text.isEmpty) {
return;
}
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.post(
'/cgi/uc/stickers/packs',
data: {
'name': _nameController.text,
'prefix': _prefixController.text,
'description': _descriptionController.text,
},
);
if (!mounted) return;
Navigator.pop(context, true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
@override
void dispose() {
_nameController.dispose();
_prefixController.dispose();
_descriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('stickersPackNew').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerPackName'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _prefixController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerPackPrefix'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _descriptionController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerPackDescription'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: [
TextButton(
onPressed: _isBusy
? null
: () {
Navigator.pop(context);
},
child: Text('dialogDismiss').tr(),
),
TextButton(
onPressed: _isBusy ? null : () => _createPack(),
child: Text('dialogConfirm').tr(),
),
],
);
}
}

View File

@@ -0,0 +1,266 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
class StickerPackScreen extends StatefulWidget {
final int id;
const StickerPackScreen({super.key, required this.id});
@override
State<StickerPackScreen> createState() => _StickerPackScreenState();
}
class _StickerPackScreenState extends State<StickerPackScreen> {
SnStickerPack? _pack;
Future<void> _fetchPack() async {
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/uc/stickers/packs/${widget.id}');
_pack = SnStickerPack.fromJson(resp.data);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
bool _isBusy = false;
Future<void> _deleteSticker(SnSticker sticker) async {
final confirm = await context.showConfirmDialog(
'stickersDelete'.tr(args: [sticker.name]),
'stickersDeleteDescription'.tr(),
);
if (!confirm) return;
if (!mounted) return;
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/uc/stickers/${sticker.id}');
if (!mounted) return;
context.showSnackbar('stickersDeleted'.tr());
_fetchPack();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchPack();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text(_pack?.name ?? 'loading'.tr()),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(isActive: _isBusy),
if (_pack != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_pack!.name).bold(),
Text(
_pack!.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
).padding(horizontal: 24, vertical: 16),
const Divider(height: 1),
ListTile(
leading: const Icon(Symbols.add),
title: Text('stickersNew').tr(),
subtitle: Text('stickersNewDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () {
showDialog(
context: context,
builder: (context) => _StickerCreateDialog(pack: _pack!),
).then((value) {
if (value) _fetchPack();
});
},
),
const Divider(height: 1),
if (_pack?.stickers != null)
Expanded(
child: GridView.extent(
padding: EdgeInsets.only(left: 20, right: 20, top: 16),
maxCrossAxisExtent: 48,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: _pack!.stickers!
.map(
(ele) => GestureDetector(
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
child: AttachmentItem(
data: ele.attachment,
heroTag: 'sticker-pack-${ele.attachment.rid}',
fit: BoxFit.contain,
),
),
),
onTap: () {
_deleteSticker(ele);
},
),
)
.toList(),
),
),
],
),
);
}
}
class _StickerCreateDialog extends StatefulWidget {
final SnStickerPack pack;
const _StickerCreateDialog({required this.pack});
@override
State<_StickerCreateDialog> createState() => _StickerCreateDialogState();
}
class _StickerCreateDialogState extends State<_StickerCreateDialog> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _aliasController = TextEditingController();
final TextEditingController _attachmentController = TextEditingController();
bool _isBusy = false;
@override
void dispose() {
_nameController.dispose();
_aliasController.dispose();
_attachmentController.dispose();
super.dispose();
}
Future<void> _createSticker() async {
if (_nameController.text.isEmpty ||
_aliasController.text.isEmpty ||
_attachmentController.text.isEmpty) {
return;
}
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.post(
'/cgi/uc/stickers',
data: {
'name': _nameController.text,
'alias': _aliasController.text,
'attachment_id': _attachmentController.text,
'pack_id': widget.pack.id,
},
);
if (!mounted) return;
Navigator.pop(context, true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('stickersNew'.tr()),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerName'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _aliasController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerAlias'.tr(),
helperText: 'fieldStickerAliasHint'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _attachmentController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldStickerAttachment'.tr(),
),
readOnly: true,
onTap: () async {
final attachment = await showDialog<SnAttachment?>(
context: context,
builder: (context) => AttachmentInputDialog(
title: 'fieldStickerAttachment'.tr(),
pool: 'sticker',
mediaType: SnMediaType.image,
),
);
if (attachment != null) {
setState(() {
_attachmentController.text = attachment.rid;
});
}
},
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: [
TextButton(
onPressed: _isBusy
? null
: () {
Navigator.pop(context);
},
child: Text('dialogDismiss').tr(),
),
TextButton(
onPressed: _isBusy ? null : () => _createSticker(),
child: Text('dialogConfirm').tr(),
),
],
);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
part 'account.freezed.dart';
part 'account.g.dart';
@@ -9,7 +8,7 @@ class SnAccount with _$SnAccount {
const SnAccount._();
const factory SnAccount({
@HiveField(0) required int id,
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,

View File

@@ -20,7 +20,6 @@ SnAccount _$SnAccountFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$SnAccount {
@HiveField(0)
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
@@ -58,7 +57,7 @@ abstract class $SnAccountCopyWith<$Res> {
_$SnAccountCopyWithImpl<$Res, SnAccount>;
@useResult
$Res call(
{@HiveField(0) int id,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
@@ -226,7 +225,7 @@ abstract class _$$SnAccountImplCopyWith<$Res>
@override
@useResult
$Res call(
{@HiveField(0) int id,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
@@ -374,7 +373,7 @@ class __$$SnAccountImplCopyWithImpl<$Res>
@JsonSerializable()
class _$SnAccountImpl extends _SnAccount {
const _$SnAccountImpl(
{@HiveField(0) required this.id,
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
@@ -403,7 +402,6 @@ class _$SnAccountImpl extends _SnAccount {
_$$SnAccountImplFromJson(json);
@override
@HiveField(0)
final int id;
@override
final DateTime createdAt;
@@ -556,7 +554,7 @@ class _$SnAccountImpl extends _SnAccount {
abstract class _SnAccount extends SnAccount {
const factory _SnAccount(
{@HiveField(0) required final int id,
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
@@ -582,7 +580,6 @@ abstract class _SnAccount extends SnAccount {
_$SnAccountImpl.fromJson;
@override
@HiveField(0)
int get id;
@override
DateTime get createdAt;

View File

@@ -1,5 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:surface/types/account.dart';
import 'package:surface/types/attachment.dart';
@@ -12,23 +11,22 @@ part 'chat.g.dart';
class SnChannel with _$SnChannel {
const SnChannel._();
@HiveType(typeId: 2)
const factory SnChannel({
@HiveField(0) required int id,
@HiveField(1) required DateTime createdAt,
@HiveField(2) required DateTime updatedAt,
@HiveField(3) required dynamic deletedAt,
@HiveField(4) required String alias,
@HiveField(5) required String name,
@HiveField(6) required String description,
@HiveField(7) required List<SnChannelMember>? members,
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required dynamic deletedAt,
required String alias,
required String name,
required String description,
required List<SnChannelMember>? members,
List<SnChatMessage>? messages,
@HiveField(8) required int type,
@HiveField(9) required int accountId,
@HiveField(10) required SnRealm? realm,
@HiveField(11) required int? realmId,
@HiveField(12) required bool isPublic,
@HiveField(13) required bool isCommunity,
required int type,
required int accountId,
required SnRealm? realm,
required int? realmId,
required bool isPublic,
required bool isCommunity,
}) = _SnChannel;
factory SnChannel.fromJson(Map<String, dynamic> json) =>
@@ -42,19 +40,18 @@ class SnChannel with _$SnChannel {
class SnChannelMember with _$SnChannelMember {
const SnChannelMember._();
@HiveType(typeId: 3)
const factory SnChannelMember({
@HiveField(0) required int id,
@HiveField(1) required DateTime createdAt,
@HiveField(2) required DateTime updatedAt,
@HiveField(3) required DateTime? deletedAt,
@HiveField(4) required int channelId,
@HiveField(5) required int accountId,
@HiveField(6) required String? nick,
@HiveField(7) required SnChannel? channel,
@HiveField(8) required SnAccount? account,
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required int channelId,
required int accountId,
required String? nick,
required SnChannel? channel,
required SnAccount? account,
@Default(0) int notify,
@HiveField(9) required int powerLevel,
required int powerLevel,
dynamic calls,
dynamic events,
}) = _SnChannelMember;
@@ -67,21 +64,20 @@ class SnChannelMember with _$SnChannelMember {
class SnChatMessage with _$SnChatMessage {
const SnChatMessage._();
@HiveType(typeId: 4)
const factory SnChatMessage({
@HiveField(0) required int id,
@HiveField(1) required DateTime createdAt,
@HiveField(2) required DateTime updatedAt,
@HiveField(3) required DateTime? deletedAt,
@HiveField(4) required String uuid,
@HiveField(5) @Default({}) Map<String, dynamic> body,
@HiveField(6) required String type,
@HiveField(7) required SnChannel channel,
@HiveField(8) required SnChannelMember sender,
@HiveField(9) required int channelId,
@HiveField(10) required int senderId,
@HiveField(11) required int? quoteEventId,
@HiveField(12) required int? relatedEventId,
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String uuid,
@Default({}) Map<String, dynamic> body,
required String type,
required SnChannel channel,
required SnChannelMember sender,
required int channelId,
required int senderId,
required int? quoteEventId,
required int? relatedEventId,
SnChatMessagePreload? preload,
}) = _SnChatMessage;

View File

@@ -20,34 +20,20 @@ SnChannel _$SnChannelFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$SnChannel {
@HiveField(0)
int get id => throw _privateConstructorUsedError;
@HiveField(1)
DateTime get createdAt => throw _privateConstructorUsedError;
@HiveField(2)
DateTime get updatedAt => throw _privateConstructorUsedError;
@HiveField(3)
dynamic get deletedAt => throw _privateConstructorUsedError;
@HiveField(4)
String get alias => throw _privateConstructorUsedError;
@HiveField(5)
String get name => throw _privateConstructorUsedError;
@HiveField(6)
String get description => throw _privateConstructorUsedError;
@HiveField(7)
List<SnChannelMember>? get members => throw _privateConstructorUsedError;
List<SnChatMessage>? get messages => throw _privateConstructorUsedError;
@HiveField(8)
int get type => throw _privateConstructorUsedError;
@HiveField(9)
int get accountId => throw _privateConstructorUsedError;
@HiveField(10)
SnRealm? get realm => throw _privateConstructorUsedError;
@HiveField(11)
int? get realmId => throw _privateConstructorUsedError;
@HiveField(12)
bool get isPublic => throw _privateConstructorUsedError;
@HiveField(13)
bool get isCommunity => throw _privateConstructorUsedError;
/// Serializes this SnChannel to a JSON map.
@@ -66,21 +52,21 @@ abstract class $SnChannelCopyWith<$Res> {
_$SnChannelCopyWithImpl<$Res, SnChannel>;
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) dynamic deletedAt,
@HiveField(4) String alias,
@HiveField(5) String name,
@HiveField(6) String description,
@HiveField(7) List<SnChannelMember>? members,
{int id,
DateTime createdAt,
DateTime updatedAt,
dynamic deletedAt,
String alias,
String name,
String description,
List<SnChannelMember>? members,
List<SnChatMessage>? messages,
@HiveField(8) int type,
@HiveField(9) int accountId,
@HiveField(10) SnRealm? realm,
@HiveField(11) int? realmId,
@HiveField(12) bool isPublic,
@HiveField(13) bool isCommunity});
int type,
int accountId,
SnRealm? realm,
int? realmId,
bool isPublic,
bool isCommunity});
$SnRealmCopyWith<$Res>? get realm;
}
@@ -204,21 +190,21 @@ abstract class _$$SnChannelImplCopyWith<$Res>
@override
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) dynamic deletedAt,
@HiveField(4) String alias,
@HiveField(5) String name,
@HiveField(6) String description,
@HiveField(7) List<SnChannelMember>? members,
{int id,
DateTime createdAt,
DateTime updatedAt,
dynamic deletedAt,
String alias,
String name,
String description,
List<SnChannelMember>? members,
List<SnChatMessage>? messages,
@HiveField(8) int type,
@HiveField(9) int accountId,
@HiveField(10) SnRealm? realm,
@HiveField(11) int? realmId,
@HiveField(12) bool isPublic,
@HiveField(13) bool isCommunity});
int type,
int accountId,
SnRealm? realm,
int? realmId,
bool isPublic,
bool isCommunity});
@override
$SnRealmCopyWith<$Res>? get realm;
@@ -320,24 +306,23 @@ class __$$SnChannelImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
@HiveType(typeId: 2)
class _$SnChannelImpl extends _SnChannel {
const _$SnChannelImpl(
{@HiveField(0) required this.id,
@HiveField(1) required this.createdAt,
@HiveField(2) required this.updatedAt,
@HiveField(3) required this.deletedAt,
@HiveField(4) required this.alias,
@HiveField(5) required this.name,
@HiveField(6) required this.description,
@HiveField(7) required final List<SnChannelMember>? members,
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.description,
required final List<SnChannelMember>? members,
final List<SnChatMessage>? messages,
@HiveField(8) required this.type,
@HiveField(9) required this.accountId,
@HiveField(10) required this.realm,
@HiveField(11) required this.realmId,
@HiveField(12) required this.isPublic,
@HiveField(13) required this.isCommunity})
required this.type,
required this.accountId,
required this.realm,
required this.realmId,
required this.isPublic,
required this.isCommunity})
: _members = members,
_messages = messages,
super._();
@@ -346,29 +331,21 @@ class _$SnChannelImpl extends _SnChannel {
_$$SnChannelImplFromJson(json);
@override
@HiveField(0)
final int id;
@override
@HiveField(1)
final DateTime createdAt;
@override
@HiveField(2)
final DateTime updatedAt;
@override
@HiveField(3)
final dynamic deletedAt;
@override
@HiveField(4)
final String alias;
@override
@HiveField(5)
final String name;
@override
@HiveField(6)
final String description;
final List<SnChannelMember>? _members;
@override
@HiveField(7)
List<SnChannelMember>? get members {
final value = _members;
if (value == null) return null;
@@ -388,22 +365,16 @@ class _$SnChannelImpl extends _SnChannel {
}
@override
@HiveField(8)
final int type;
@override
@HiveField(9)
final int accountId;
@override
@HiveField(10)
final SnRealm? realm;
@override
@HiveField(11)
final int? realmId;
@override
@HiveField(12)
final bool isPublic;
@override
@HiveField(13)
final bool isCommunity;
@override
@@ -477,69 +448,55 @@ class _$SnChannelImpl extends _SnChannel {
abstract class _SnChannel extends SnChannel {
const factory _SnChannel(
{@HiveField(0) required final int id,
@HiveField(1) required final DateTime createdAt,
@HiveField(2) required final DateTime updatedAt,
@HiveField(3) required final dynamic deletedAt,
@HiveField(4) required final String alias,
@HiveField(5) required final String name,
@HiveField(6) required final String description,
@HiveField(7) required final List<SnChannelMember>? members,
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final dynamic deletedAt,
required final String alias,
required final String name,
required final String description,
required final List<SnChannelMember>? members,
final List<SnChatMessage>? messages,
@HiveField(8) required final int type,
@HiveField(9) required final int accountId,
@HiveField(10) required final SnRealm? realm,
@HiveField(11) required final int? realmId,
@HiveField(12) required final bool isPublic,
@HiveField(13) required final bool isCommunity}) = _$SnChannelImpl;
required final int type,
required final int accountId,
required final SnRealm? realm,
required final int? realmId,
required final bool isPublic,
required final bool isCommunity}) = _$SnChannelImpl;
const _SnChannel._() : super._();
factory _SnChannel.fromJson(Map<String, dynamic> json) =
_$SnChannelImpl.fromJson;
@override
@HiveField(0)
int get id;
@override
@HiveField(1)
DateTime get createdAt;
@override
@HiveField(2)
DateTime get updatedAt;
@override
@HiveField(3)
dynamic get deletedAt;
@override
@HiveField(4)
String get alias;
@override
@HiveField(5)
String get name;
@override
@HiveField(6)
String get description;
@override
@HiveField(7)
List<SnChannelMember>? get members;
@override
List<SnChatMessage>? get messages;
@override
@HiveField(8)
int get type;
@override
@HiveField(9)
int get accountId;
@override
@HiveField(10)
SnRealm? get realm;
@override
@HiveField(11)
int? get realmId;
@override
@HiveField(12)
bool get isPublic;
@override
@HiveField(13)
bool get isCommunity;
/// Create a copy of SnChannel
@@ -556,26 +513,16 @@ SnChannelMember _$SnChannelMemberFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$SnChannelMember {
@HiveField(0)
int get id => throw _privateConstructorUsedError;
@HiveField(1)
DateTime get createdAt => throw _privateConstructorUsedError;
@HiveField(2)
DateTime get updatedAt => throw _privateConstructorUsedError;
@HiveField(3)
DateTime? get deletedAt => throw _privateConstructorUsedError;
@HiveField(4)
int get channelId => throw _privateConstructorUsedError;
@HiveField(5)
int get accountId => throw _privateConstructorUsedError;
@HiveField(6)
String? get nick => throw _privateConstructorUsedError;
@HiveField(7)
SnChannel? get channel => throw _privateConstructorUsedError;
@HiveField(8)
SnAccount? get account => throw _privateConstructorUsedError;
int get notify => throw _privateConstructorUsedError;
@HiveField(9)
int get powerLevel => throw _privateConstructorUsedError;
dynamic get calls => throw _privateConstructorUsedError;
dynamic get events => throw _privateConstructorUsedError;
@@ -597,17 +544,17 @@ abstract class $SnChannelMemberCopyWith<$Res> {
_$SnChannelMemberCopyWithImpl<$Res, SnChannelMember>;
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) int channelId,
@HiveField(5) int accountId,
@HiveField(6) String? nick,
@HiveField(7) SnChannel? channel,
@HiveField(8) SnAccount? account,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int channelId,
int accountId,
String? nick,
SnChannel? channel,
SnAccount? account,
int notify,
@HiveField(9) int powerLevel,
int powerLevel,
dynamic calls,
dynamic events});
@@ -738,17 +685,17 @@ abstract class _$$SnChannelMemberImplCopyWith<$Res>
@override
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) int channelId,
@HiveField(5) int accountId,
@HiveField(6) String? nick,
@HiveField(7) SnChannel? channel,
@HiveField(8) SnAccount? account,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int channelId,
int accountId,
String? nick,
SnChannel? channel,
SnAccount? account,
int notify,
@HiveField(9) int powerLevel,
int powerLevel,
dynamic calls,
dynamic events});
@@ -844,20 +791,19 @@ class __$$SnChannelMemberImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
@HiveType(typeId: 3)
class _$SnChannelMemberImpl extends _SnChannelMember {
const _$SnChannelMemberImpl(
{@HiveField(0) required this.id,
@HiveField(1) required this.createdAt,
@HiveField(2) required this.updatedAt,
@HiveField(3) required this.deletedAt,
@HiveField(4) required this.channelId,
@HiveField(5) required this.accountId,
@HiveField(6) required this.nick,
@HiveField(7) required this.channel,
@HiveField(8) required this.account,
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.channelId,
required this.accountId,
required this.nick,
required this.channel,
required this.account,
this.notify = 0,
@HiveField(9) required this.powerLevel,
required this.powerLevel,
this.calls,
this.events})
: super._();
@@ -866,37 +812,27 @@ class _$SnChannelMemberImpl extends _SnChannelMember {
_$$SnChannelMemberImplFromJson(json);
@override
@HiveField(0)
final int id;
@override
@HiveField(1)
final DateTime createdAt;
@override
@HiveField(2)
final DateTime updatedAt;
@override
@HiveField(3)
final DateTime? deletedAt;
@override
@HiveField(4)
final int channelId;
@override
@HiveField(5)
final int accountId;
@override
@HiveField(6)
final String? nick;
@override
@HiveField(7)
final SnChannel? channel;
@override
@HiveField(8)
final SnAccount? account;
@override
@JsonKey()
final int notify;
@override
@HiveField(9)
final int powerLevel;
@override
final dynamic calls;
@@ -971,17 +907,17 @@ class _$SnChannelMemberImpl extends _SnChannelMember {
abstract class _SnChannelMember extends SnChannelMember {
const factory _SnChannelMember(
{@HiveField(0) required final int id,
@HiveField(1) required final DateTime createdAt,
@HiveField(2) required final DateTime updatedAt,
@HiveField(3) required final DateTime? deletedAt,
@HiveField(4) required final int channelId,
@HiveField(5) required final int accountId,
@HiveField(6) required final String? nick,
@HiveField(7) required final SnChannel? channel,
@HiveField(8) required final SnAccount? account,
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final int channelId,
required final int accountId,
required final String? nick,
required final SnChannel? channel,
required final SnAccount? account,
final int notify,
@HiveField(9) required final int powerLevel,
required final int powerLevel,
final dynamic calls,
final dynamic events}) = _$SnChannelMemberImpl;
const _SnChannelMember._() : super._();
@@ -990,36 +926,26 @@ abstract class _SnChannelMember extends SnChannelMember {
_$SnChannelMemberImpl.fromJson;
@override
@HiveField(0)
int get id;
@override
@HiveField(1)
DateTime get createdAt;
@override
@HiveField(2)
DateTime get updatedAt;
@override
@HiveField(3)
DateTime? get deletedAt;
@override
@HiveField(4)
int get channelId;
@override
@HiveField(5)
int get accountId;
@override
@HiveField(6)
String? get nick;
@override
@HiveField(7)
SnChannel? get channel;
@override
@HiveField(8)
SnAccount? get account;
@override
int get notify;
@override
@HiveField(9)
int get powerLevel;
@override
dynamic get calls;
@@ -1040,31 +966,18 @@ SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$SnChatMessage {
@HiveField(0)
int get id => throw _privateConstructorUsedError;
@HiveField(1)
DateTime get createdAt => throw _privateConstructorUsedError;
@HiveField(2)
DateTime get updatedAt => throw _privateConstructorUsedError;
@HiveField(3)
DateTime? get deletedAt => throw _privateConstructorUsedError;
@HiveField(4)
String get uuid => throw _privateConstructorUsedError;
@HiveField(5)
Map<String, dynamic> get body => throw _privateConstructorUsedError;
@HiveField(6)
String get type => throw _privateConstructorUsedError;
@HiveField(7)
SnChannel get channel => throw _privateConstructorUsedError;
@HiveField(8)
SnChannelMember get sender => throw _privateConstructorUsedError;
@HiveField(9)
int get channelId => throw _privateConstructorUsedError;
@HiveField(10)
int get senderId => throw _privateConstructorUsedError;
@HiveField(11)
int? get quoteEventId => throw _privateConstructorUsedError;
@HiveField(12)
int? get relatedEventId => throw _privateConstructorUsedError;
SnChatMessagePreload? get preload => throw _privateConstructorUsedError;
@@ -1085,19 +998,19 @@ abstract class $SnChatMessageCopyWith<$Res> {
_$SnChatMessageCopyWithImpl<$Res, SnChatMessage>;
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) String uuid,
@HiveField(5) Map<String, dynamic> body,
@HiveField(6) String type,
@HiveField(7) SnChannel channel,
@HiveField(8) SnChannelMember sender,
@HiveField(9) int channelId,
@HiveField(10) int senderId,
@HiveField(11) int? quoteEventId,
@HiveField(12) int? relatedEventId,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String uuid,
Map<String, dynamic> body,
String type,
SnChannel channel,
SnChannelMember sender,
int channelId,
int senderId,
int? quoteEventId,
int? relatedEventId,
SnChatMessagePreload? preload});
$SnChannelCopyWith<$Res> get channel;
@@ -1239,19 +1152,19 @@ abstract class _$$SnChatMessageImplCopyWith<$Res>
@override
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) String uuid,
@HiveField(5) Map<String, dynamic> body,
@HiveField(6) String type,
@HiveField(7) SnChannel channel,
@HiveField(8) SnChannelMember sender,
@HiveField(9) int channelId,
@HiveField(10) int senderId,
@HiveField(11) int? quoteEventId,
@HiveField(12) int? relatedEventId,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String uuid,
Map<String, dynamic> body,
String type,
SnChannel channel,
SnChannelMember sender,
int channelId,
int senderId,
int? quoteEventId,
int? relatedEventId,
SnChatMessagePreload? preload});
@override
@@ -1353,22 +1266,21 @@ class __$$SnChatMessageImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
@HiveType(typeId: 4)
class _$SnChatMessageImpl extends _SnChatMessage {
const _$SnChatMessageImpl(
{@HiveField(0) required this.id,
@HiveField(1) required this.createdAt,
@HiveField(2) required this.updatedAt,
@HiveField(3) required this.deletedAt,
@HiveField(4) required this.uuid,
@HiveField(5) final Map<String, dynamic> body = const {},
@HiveField(6) required this.type,
@HiveField(7) required this.channel,
@HiveField(8) required this.sender,
@HiveField(9) required this.channelId,
@HiveField(10) required this.senderId,
@HiveField(11) required this.quoteEventId,
@HiveField(12) required this.relatedEventId,
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.uuid,
final Map<String, dynamic> body = const {},
required this.type,
required this.channel,
required this.sender,
required this.channelId,
required this.senderId,
required this.quoteEventId,
required this.relatedEventId,
this.preload})
: _body = body,
super._();
@@ -1377,24 +1289,18 @@ class _$SnChatMessageImpl extends _SnChatMessage {
_$$SnChatMessageImplFromJson(json);
@override
@HiveField(0)
final int id;
@override
@HiveField(1)
final DateTime createdAt;
@override
@HiveField(2)
final DateTime updatedAt;
@override
@HiveField(3)
final DateTime? deletedAt;
@override
@HiveField(4)
final String uuid;
final Map<String, dynamic> _body;
@override
@JsonKey()
@HiveField(5)
Map<String, dynamic> get body {
if (_body is EqualUnmodifiableMapView) return _body;
// ignore: implicit_dynamic_type
@@ -1402,25 +1308,18 @@ class _$SnChatMessageImpl extends _SnChatMessage {
}
@override
@HiveField(6)
final String type;
@override
@HiveField(7)
final SnChannel channel;
@override
@HiveField(8)
final SnChannelMember sender;
@override
@HiveField(9)
final int channelId;
@override
@HiveField(10)
final int senderId;
@override
@HiveField(11)
final int? quoteEventId;
@override
@HiveField(12)
final int? relatedEventId;
@override
final SnChatMessagePreload? preload;
@@ -1495,19 +1394,19 @@ class _$SnChatMessageImpl extends _SnChatMessage {
abstract class _SnChatMessage extends SnChatMessage {
const factory _SnChatMessage(
{@HiveField(0) required final int id,
@HiveField(1) required final DateTime createdAt,
@HiveField(2) required final DateTime updatedAt,
@HiveField(3) required final DateTime? deletedAt,
@HiveField(4) required final String uuid,
@HiveField(5) final Map<String, dynamic> body,
@HiveField(6) required final String type,
@HiveField(7) required final SnChannel channel,
@HiveField(8) required final SnChannelMember sender,
@HiveField(9) required final int channelId,
@HiveField(10) required final int senderId,
@HiveField(11) required final int? quoteEventId,
@HiveField(12) required final int? relatedEventId,
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String uuid,
final Map<String, dynamic> body,
required final String type,
required final SnChannel channel,
required final SnChannelMember sender,
required final int channelId,
required final int senderId,
required final int? quoteEventId,
required final int? relatedEventId,
final SnChatMessagePreload? preload}) = _$SnChatMessageImpl;
const _SnChatMessage._() : super._();
@@ -1515,43 +1414,30 @@ abstract class _SnChatMessage extends SnChatMessage {
_$SnChatMessageImpl.fromJson;
@override
@HiveField(0)
int get id;
@override
@HiveField(1)
DateTime get createdAt;
@override
@HiveField(2)
DateTime get updatedAt;
@override
@HiveField(3)
DateTime? get deletedAt;
@override
@HiveField(4)
String get uuid;
@override
@HiveField(5)
Map<String, dynamic> get body;
@override
@HiveField(6)
String get type;
@override
@HiveField(7)
SnChannel get channel;
@override
@HiveField(8)
SnChannelMember get sender;
@override
@HiveField(9)
int get channelId;
@override
@HiveField(10)
int get senderId;
@override
@HiveField(11)
int? get quoteEventId;
@override
@HiveField(12)
int? get relatedEventId;
@override
SnChatMessagePreload? get preload;

View File

@@ -2,214 +2,6 @@
part of 'chat.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
@override
final int typeId = 2;
@override
_$SnChannelImpl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return _$SnChannelImpl(
id: fields[0] as int,
createdAt: fields[1] as DateTime,
updatedAt: fields[2] as DateTime,
deletedAt: fields[3] as dynamic,
alias: fields[4] as String,
name: fields[5] as String,
description: fields[6] as String,
members: (fields[7] as List?)?.cast<SnChannelMember>(),
type: fields[8] as int,
accountId: fields[9] as int,
realm: fields[10] as SnRealm?,
realmId: fields[11] as int?,
isPublic: fields[12] as bool,
isCommunity: fields[13] as bool,
);
}
@override
void write(BinaryWriter writer, _$SnChannelImpl obj) {
writer
..writeByte(14)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.createdAt)
..writeByte(2)
..write(obj.updatedAt)
..writeByte(3)
..write(obj.deletedAt)
..writeByte(4)
..write(obj.alias)
..writeByte(5)
..write(obj.name)
..writeByte(6)
..write(obj.description)
..writeByte(8)
..write(obj.type)
..writeByte(9)
..write(obj.accountId)
..writeByte(10)
..write(obj.realm)
..writeByte(11)
..write(obj.realmId)
..writeByte(12)
..write(obj.isPublic)
..writeByte(13)
..write(obj.isCommunity)
..writeByte(7)
..write(obj.members);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SnChannelImplAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class SnChannelMemberImplAdapter extends TypeAdapter<_$SnChannelMemberImpl> {
@override
final int typeId = 3;
@override
_$SnChannelMemberImpl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return _$SnChannelMemberImpl(
id: fields[0] as int,
createdAt: fields[1] as DateTime,
updatedAt: fields[2] as DateTime,
deletedAt: fields[3] as DateTime?,
channelId: fields[4] as int,
accountId: fields[5] as int,
nick: fields[6] as String?,
channel: fields[7] as SnChannel?,
account: fields[8] as SnAccount?,
powerLevel: fields[9] as int,
);
}
@override
void write(BinaryWriter writer, _$SnChannelMemberImpl obj) {
writer
..writeByte(10)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.createdAt)
..writeByte(2)
..write(obj.updatedAt)
..writeByte(3)
..write(obj.deletedAt)
..writeByte(4)
..write(obj.channelId)
..writeByte(5)
..write(obj.accountId)
..writeByte(6)
..write(obj.nick)
..writeByte(7)
..write(obj.channel)
..writeByte(8)
..write(obj.account)
..writeByte(9)
..write(obj.powerLevel);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SnChannelMemberImplAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
@override
final int typeId = 4;
@override
_$SnChatMessageImpl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return _$SnChatMessageImpl(
id: fields[0] as int,
createdAt: fields[1] as DateTime,
updatedAt: fields[2] as DateTime,
deletedAt: fields[3] as DateTime?,
uuid: fields[4] as String,
body: (fields[5] as Map).cast<String, dynamic>(),
type: fields[6] as String,
channel: fields[7] as SnChannel,
sender: fields[8] as SnChannelMember,
channelId: fields[9] as int,
senderId: fields[10] as int,
quoteEventId: fields[11] as int?,
relatedEventId: fields[12] as int?,
);
}
@override
void write(BinaryWriter writer, _$SnChatMessageImpl obj) {
writer
..writeByte(13)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.createdAt)
..writeByte(2)
..write(obj.updatedAt)
..writeByte(3)
..write(obj.deletedAt)
..writeByte(4)
..write(obj.uuid)
..writeByte(6)
..write(obj.type)
..writeByte(7)
..write(obj.channel)
..writeByte(8)
..write(obj.sender)
..writeByte(9)
..write(obj.channelId)
..writeByte(10)
..write(obj.senderId)
..writeByte(11)
..write(obj.quoteEventId)
..writeByte(12)
..write(obj.relatedEventId)
..writeByte(5)
..write(obj.body);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SnChatMessageImplAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

View File

@@ -1,5 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:surface/types/account.dart';
part 'realm.freezed.dart';
@@ -27,22 +26,21 @@ class SnRealmMember with _$SnRealmMember {
class SnRealm with _$SnRealm {
const SnRealm._();
@HiveType(typeId: 1)
const factory SnRealm({
@HiveField(0) required int id,
@HiveField(1) required DateTime createdAt,
@HiveField(2) required DateTime updatedAt,
@HiveField(3) required DateTime? deletedAt,
@HiveField(4) required String alias,
@HiveField(5) required String name,
@HiveField(6) required String description,
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String alias,
required String name,
required String description,
List<SnRealmMember>? members,
@HiveField(7) required String? avatar,
@HiveField(8) required String? banner,
@HiveField(9) required Map<String, dynamic>? accessPolicy,
@HiveField(10) required int accountId,
@HiveField(11) required bool isPublic,
@HiveField(12) required bool isCommunity,
required String? avatar,
required String? banner,
required Map<String, dynamic>? accessPolicy,
required int accountId,
required bool isPublic,
required bool isCommunity,
@Default(0) int popularity,
}) = _SnRealm;

View File

@@ -367,32 +367,19 @@ SnRealm _$SnRealmFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$SnRealm {
@HiveField(0)
int get id => throw _privateConstructorUsedError;
@HiveField(1)
DateTime get createdAt => throw _privateConstructorUsedError;
@HiveField(2)
DateTime get updatedAt => throw _privateConstructorUsedError;
@HiveField(3)
DateTime? get deletedAt => throw _privateConstructorUsedError;
@HiveField(4)
String get alias => throw _privateConstructorUsedError;
@HiveField(5)
String get name => throw _privateConstructorUsedError;
@HiveField(6)
String get description => throw _privateConstructorUsedError;
List<SnRealmMember>? get members => throw _privateConstructorUsedError;
@HiveField(7)
String? get avatar => throw _privateConstructorUsedError;
@HiveField(8)
String? get banner => throw _privateConstructorUsedError;
@HiveField(9)
Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError;
@HiveField(10)
int get accountId => throw _privateConstructorUsedError;
@HiveField(11)
bool get isPublic => throw _privateConstructorUsedError;
@HiveField(12)
bool get isCommunity => throw _privateConstructorUsedError;
int get popularity => throw _privateConstructorUsedError;
@@ -411,20 +398,20 @@ abstract class $SnRealmCopyWith<$Res> {
_$SnRealmCopyWithImpl<$Res, SnRealm>;
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) String alias,
@HiveField(5) String name,
@HiveField(6) String description,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
List<SnRealmMember>? members,
@HiveField(7) String? avatar,
@HiveField(8) String? banner,
@HiveField(9) Map<String, dynamic>? accessPolicy,
@HiveField(10) int accountId,
@HiveField(11) bool isPublic,
@HiveField(12) bool isCommunity,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
int accountId,
bool isPublic,
bool isCommunity,
int popularity});
}
@@ -532,20 +519,20 @@ abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
@override
@useResult
$Res call(
{@HiveField(0) int id,
@HiveField(1) DateTime createdAt,
@HiveField(2) DateTime updatedAt,
@HiveField(3) DateTime? deletedAt,
@HiveField(4) String alias,
@HiveField(5) String name,
@HiveField(6) String description,
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
List<SnRealmMember>? members,
@HiveField(7) String? avatar,
@HiveField(8) String? banner,
@HiveField(9) Map<String, dynamic>? accessPolicy,
@HiveField(10) int accountId,
@HiveField(11) bool isPublic,
@HiveField(12) bool isCommunity,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
int accountId,
bool isPublic,
bool isCommunity,
int popularity});
}
@@ -645,23 +632,22 @@ class __$$SnRealmImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
@HiveType(typeId: 1)
class _$SnRealmImpl extends _SnRealm {
const _$SnRealmImpl(
{@HiveField(0) required this.id,
@HiveField(1) required this.createdAt,
@HiveField(2) required this.updatedAt,
@HiveField(3) required this.deletedAt,
@HiveField(4) required this.alias,
@HiveField(5) required this.name,
@HiveField(6) required this.description,
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.description,
final List<SnRealmMember>? members,
@HiveField(7) required this.avatar,
@HiveField(8) required this.banner,
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
@HiveField(10) required this.accountId,
@HiveField(11) required this.isPublic,
@HiveField(12) required this.isCommunity,
required this.avatar,
required this.banner,
required final Map<String, dynamic>? accessPolicy,
required this.accountId,
required this.isPublic,
required this.isCommunity,
this.popularity = 0})
: _members = members,
_accessPolicy = accessPolicy,
@@ -671,25 +657,18 @@ class _$SnRealmImpl extends _SnRealm {
_$$SnRealmImplFromJson(json);
@override
@HiveField(0)
final int id;
@override
@HiveField(1)
final DateTime createdAt;
@override
@HiveField(2)
final DateTime updatedAt;
@override
@HiveField(3)
final DateTime? deletedAt;
@override
@HiveField(4)
final String alias;
@override
@HiveField(5)
final String name;
@override
@HiveField(6)
final String description;
final List<SnRealmMember>? _members;
@override
@@ -702,14 +681,11 @@ class _$SnRealmImpl extends _SnRealm {
}
@override
@HiveField(7)
final String? avatar;
@override
@HiveField(8)
final String? banner;
final Map<String, dynamic>? _accessPolicy;
@override
@HiveField(9)
Map<String, dynamic>? get accessPolicy {
final value = _accessPolicy;
if (value == null) return null;
@@ -719,13 +695,10 @@ class _$SnRealmImpl extends _SnRealm {
}
@override
@HiveField(10)
final int accountId;
@override
@HiveField(11)
final bool isPublic;
@override
@HiveField(12)
final bool isCommunity;
@override
@JsonKey()
@@ -805,65 +778,52 @@ class _$SnRealmImpl extends _SnRealm {
abstract class _SnRealm extends SnRealm {
const factory _SnRealm(
{@HiveField(0) required final int id,
@HiveField(1) required final DateTime createdAt,
@HiveField(2) required final DateTime updatedAt,
@HiveField(3) required final DateTime? deletedAt,
@HiveField(4) required final String alias,
@HiveField(5) required final String name,
@HiveField(6) required final String description,
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String alias,
required final String name,
required final String description,
final List<SnRealmMember>? members,
@HiveField(7) required final String? avatar,
@HiveField(8) required final String? banner,
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
@HiveField(10) required final int accountId,
@HiveField(11) required final bool isPublic,
@HiveField(12) required final bool isCommunity,
required final String? avatar,
required final String? banner,
required final Map<String, dynamic>? accessPolicy,
required final int accountId,
required final bool isPublic,
required final bool isCommunity,
final int popularity}) = _$SnRealmImpl;
const _SnRealm._() : super._();
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
@override
@HiveField(0)
int get id;
@override
@HiveField(1)
DateTime get createdAt;
@override
@HiveField(2)
DateTime get updatedAt;
@override
@HiveField(3)
DateTime? get deletedAt;
@override
@HiveField(4)
String get alias;
@override
@HiveField(5)
String get name;
@override
@HiveField(6)
String get description;
@override
List<SnRealmMember>? get members;
@override
@HiveField(7)
String? get avatar;
@override
@HiveField(8)
String? get banner;
@override
@HiveField(9)
Map<String, dynamic>? get accessPolicy;
@override
@HiveField(10)
int get accountId;
@override
@HiveField(11)
bool get isPublic;
@override
@HiveField(12)
bool get isCommunity;
@override
int get popularity;

View File

@@ -2,80 +2,6 @@
part of 'realm.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class SnRealmImplAdapter extends TypeAdapter<_$SnRealmImpl> {
@override
final int typeId = 1;
@override
_$SnRealmImpl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return _$SnRealmImpl(
id: fields[0] as int,
createdAt: fields[1] as DateTime,
updatedAt: fields[2] as DateTime,
deletedAt: fields[3] as DateTime?,
alias: fields[4] as String,
name: fields[5] as String,
description: fields[6] as String,
avatar: fields[7] as String?,
banner: fields[8] as String?,
accessPolicy: (fields[9] as Map?)?.cast<String, dynamic>(),
accountId: fields[10] as int,
isPublic: fields[11] as bool,
isCommunity: fields[12] as bool,
);
}
@override
void write(BinaryWriter writer, _$SnRealmImpl obj) {
writer
..writeByte(13)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.createdAt)
..writeByte(2)
..write(obj.updatedAt)
..writeByte(3)
..write(obj.deletedAt)
..writeByte(4)
..write(obj.alias)
..writeByte(5)
..write(obj.name)
..writeByte(6)
..write(obj.description)
..writeByte(7)
..write(obj.avatar)
..writeByte(8)
..write(obj.banner)
..writeByte(10)
..write(obj.accountId)
..writeByte(11)
..write(obj.isPublic)
..writeByte(12)
..write(obj.isCommunity)
..writeByte(9)
..write(obj.accessPolicy);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SnRealmImplAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

View File

@@ -109,11 +109,13 @@ class ChatMessage extends StatelessWidget {
onTap: () {
if (user == null) return;
showPopover(
backgroundColor: Theme.of(context).colorScheme.surface,
backgroundColor:
Theme.of(context).colorScheme.surface,
context: context,
transition: PopoverTransition.other,
bodyBuilder: (context) => SizedBox(
width: math.min(400, MediaQuery.of(context).size.width - 10),
width: math.min(
400, MediaQuery.of(context).size.width - 10),
child: AccountPopoverCard(
data: user,
),
@@ -144,11 +146,14 @@ class ChatMessage extends StatelessWidget {
radius: 12,
).padding(right: 8),
Text(
(data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown',
(data.sender.nick?.isNotEmpty ?? false)
? data.sender.nick!
: user?.nick ?? 'unknown',
).bold(),
const Gap(8),
Text(
dateFormatter.format(data.createdAt.toLocal()),
dateFormatter
.format(data.createdAt.toLocal()),
).fontSize(13),
],
).height(21),
@@ -159,7 +164,8 @@ class ChatMessage extends StatelessWidget {
maxWidth: 480,
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderRadius:
const BorderRadius.all(Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
@@ -207,9 +213,12 @@ class ChatMessage extends StatelessWidget {
maxHeight: 560,
maxWidth: 480,
minWidth: 480,
padding: padding.copyWith(top: 8),
padding: padding.copyWith(top: 8, left: 48 + padding.left),
),
if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8),
if (!hasMerged && !isCompact)
const Gap(12)
else if (!isCompact)
const Gap(8),
],
),
),
@@ -223,7 +232,8 @@ class _ChatMessageText extends StatelessWidget {
final Function(SnChatMessage)? onEdit;
final Function(SnChatMessage)? onDelete;
const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete});
const _ChatMessageText(
{required this.data, this.onReply, this.onEdit, this.onDelete});
@override
Widget build(BuildContext context) {
@@ -237,7 +247,8 @@ class _ChatMessageText extends StatelessWidget {
children: [
SelectionArea(
contextMenuBuilder: (context, editableTextState) {
final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems;
final List<ContextMenuButtonItem> items =
editableTextState.contextMenuButtonItems;
if (onReply != null) {
items.insert(
@@ -286,6 +297,8 @@ class _ChatMessageText extends StatelessWidget {
child: MarkdownTextContent(
content: data.body['text'],
isAutoWarp: true,
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
),
),
),

View File

@@ -44,7 +44,9 @@ class MarkdownTextContent extends StatelessWidget {
Theme.of(context),
).copyWith(
textScaler: textScaler,
p: textColor != null ? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor) : null,
p: textColor != null
? Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor)
: null,
blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
@@ -115,7 +117,7 @@ class MarkdownTextContent extends StatelessWidget {
final alias = segments[1];
final st = context.read<SnStickerProvider>();
final sn = context.read<SnNetworkProvider>();
final double size = isEnlargeSticker ? 128 : 32;
final double size = isEnlargeSticker ? 96 : 32;
return Container(
width: size,
height: size,
@@ -131,7 +133,8 @@ class MarkdownTextContent extends StatelessWidget {
if (snapshot.hasData) {
return GestureDetector(
child: UniversalImage(
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
sn.getAttachmentUrl(
snapshot.data!.attachment.rid),
fit: BoxFit.contain,
width: size,
height: size,
@@ -177,7 +180,9 @@ class MarkdownTextContent extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: attachment.metadata['ratio'] ??
switch (attachment.mimetype.split('/').firstOrNull) {
switch (attachment.mimetype
.split('/')
.firstOrNull) {
'audio' => 16 / 9,
'video' => 16 / 9,
_ => 1,

View File

@@ -31,6 +31,7 @@ class AppScaffold extends StatelessWidget {
final AppBar? appBar;
final DrawerCallback? onDrawerChanged;
final DrawerCallback? onEndDrawerChanged;
final bool noBackground;
const AppScaffold({
super.key,
@@ -45,6 +46,7 @@ class AppScaffold extends StatelessWidget {
this.endDrawer,
this.onDrawerChanged,
this.onEndDrawerChanged,
this.noBackground = false,
});
@override
@@ -52,20 +54,23 @@ class AppScaffold extends StatelessWidget {
final appBarHeight = appBar?.preferredSize.height ?? 0;
final safeTop = MediaQuery.of(context).padding.top;
final content = Column(
children: [
IgnorePointer(
child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0),
),
if (body != null) Expanded(child: body!),
],
);
return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SizedBox.expand(
child: AppBackground(
isRoot: true,
child: Column(
children: [
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
if (body != null) Expanded(child: body!),
],
),
),
child: noBackground
? content
: AppBackground(isRoot: true, child: content),
),
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
@@ -107,11 +112,19 @@ class AppRootScaffold extends StatelessWidget {
final isCollapseDrawer = cfg.drawerIsCollapsed;
final isExpandedDrawer = cfg.drawerIsExpanded;
final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name;
final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName)
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
: false;
final isPopable = !NavigationProvider.kAllDestination.map((ele) => ele.screen).contains(routeName);
final routeName = GoRouter.of(context)
.routerDelegate
.currentConfiguration
.last
.route
.name;
final isShowBottomNavigation =
NavigationProvider.kShowBottomNavScreen.contains(routeName)
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
: false;
final isPopable = !NavigationProvider.kAllDestination
.map((ele) => ele.screen)
.contains(routeName);
final innerWidget = isCollapseDrawer
? body
@@ -126,7 +139,9 @@ class AppRootScaffold extends StatelessWidget {
),
),
),
child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
child: isExpandedDrawer
? AppNavigationDrawer(elevation: 0)
: AppRailNavigation(),
),
Expanded(child: body),
],
@@ -150,7 +165,8 @@ class AppRootScaffold extends StatelessWidget {
children: [
Column(
children: [
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
if (!kIsWeb &&
(Platform.isWindows || Platform.isLinux || Platform.isMacOS))
WindowTitleBarBox(
child: Container(
decoration: BoxDecoration(
@@ -164,12 +180,19 @@ class AppRootScaffold extends StatelessWidget {
child: MoveWindow(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
mainAxisAlignment: Platform.isMacOS
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [
Expanded(
child: Text(
'Solar Network',
style: GoogleFonts.spaceGrotesk(),
textAlign: !kIsWeb
? Platform.isMacOS
? TextAlign.center
: null
: null,
).padding(horizontal: 12, vertical: 5),
),
if (!Platform.isMacOS)
@@ -179,9 +202,12 @@ class AppRootScaffold extends StatelessWidget {
Expanded(child: MoveWindow()),
Row(
children: [
MinimizeWindowButton(colors: windowButtonColor),
MaximizeWindowButton(colors: windowButtonColor),
CloseWindowButton(colors: windowButtonColor),
MinimizeWindowButton(
colors: windowButtonColor),
MaximizeWindowButton(
colors: windowButtonColor),
CloseWindowButton(
colors: windowButtonColor),
],
),
],
@@ -194,16 +220,28 @@ class AppRootScaffold extends StatelessWidget {
Expanded(child: innerWidget),
],
),
Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()),
Positioned(
top: safeTop > 0 ? safeTop : 16,
right: 8,
child: NotifyIndicator()),
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
Positioned(bottom: safeBottom > 0 ? safeBottom : 16, left: 0, right: 0, child: ConnectionIndicator())
Positioned(
bottom: safeBottom > 0 ? safeBottom : 16,
left: 0,
right: 0,
child: ConnectionIndicator())
else
Positioned(top: safeTop > 0 ? safeTop : 16, left: 0, right: 0, child: ConnectionIndicator()),
Positioned(
top: safeTop > 0 ? safeTop : 16,
left: 0,
right: 0,
child: ConnectionIndicator()),
],
),
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
drawerEdgeDragWidth: isPopable ? 0 : null,
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
bottomNavigationBar:
isShowBottomNavigation ? AppBottomNavigationBar() : null,
);
}
}

View File

@@ -23,7 +23,8 @@ class NotifyIndicator extends StatefulWidget {
State<NotifyIndicator> createState() => _NotifyIndicatorState();
}
class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProviderStateMixin {
class _NotifyIndicatorState extends State<NotifyIndicator>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
@@ -101,7 +102,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
padding: const EdgeInsets.symmetric(vertical: 16),
width: double.infinity,
constraints: BoxConstraints(
maxWidth: isMobile ? MediaQuery.of(context).size.width - 16 : 360,
maxWidth:
isMobile ? MediaQuery.of(context).size.width - 16 : 360,
),
child: Material(
elevation: 2,
@@ -118,7 +120,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
),
)
else
Icon(kNotificationTopicIcons[current?.topic] ?? Symbols.notifications),
Icon(kNotificationTopicIcons[current?.topic] ??
Symbols.notifications),
const Gap(16),
Expanded(
child: Column(
@@ -126,14 +129,20 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
children: [
Text(
current?.title ?? 'Notification',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.bold,
),
),
if (current?.subtitle?.isNotEmpty ?? false)
Text(
current!.subtitle!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.bold,
),
),
@@ -148,18 +157,25 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(DateFormat('HH:mm').format(current?.createdAt.toLocal() ?? DateTime.now()))
.fontSize(12)
.padding(right: 2),
Text(
DateFormat('HH:mm').format(
(current?.createdAt ?? DateTime.now())
.millisecondsSinceEpoch >
0
? (current?.createdAt ?? DateTime.now())
: DateTime.now()),
).fontSize(12).padding(right: 2),
const Gap(6),
if (current?.metadata['image'] != null)
SizedBox(
width: 40,
height: 40,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderRadius: const BorderRadius.all(
Radius.circular(8)),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(current?.metadata['image']),
sn.getAttachmentUrl(
current?.metadata['image']),
fit: BoxFit.cover,
),
),

View File

@@ -92,9 +92,10 @@ class OpenablePostItem extends StatelessWidget {
openColor: Colors.transparent,
openElevation: 0,
transitionType: ContainerTransitionType.fade,
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
),
closedColor:
Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
),
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
@@ -135,9 +136,11 @@ class PostItem extends StatelessWidget {
final box = context.findRenderObject() as RenderBox?;
final url = 'https://solsynth.dev/posts/${data.id}';
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
Share.shareUri(Uri.parse(url),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
} else {
Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
Share.share(url,
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
}
}
@@ -155,7 +158,8 @@ class PostItem extends StatelessWidget {
child: MultiProvider(
providers: [
Provider<SnNetworkProvider>(create: (_) => context.read()),
ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()),
ChangeNotifierProvider<ConfigProvider>(
create: (_) => context.read()),
],
child: ResponsiveBreakpoints.builder(
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
@@ -183,7 +187,8 @@ class PostItem extends StatelessWidget {
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
} else {
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile);
await FileSaver.instance.saveFile(
name: 'Solar Network Post #${data.id}.png', file: imageFile);
}
await imageFile.delete();
@@ -197,7 +202,9 @@ class PostItem extends StatelessWidget {
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
// Video full view
if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
if (showFullPost &&
data.type == 'video' &&
ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -217,7 +224,8 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) {}
},
).padding(bottom: 8),
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
if (data.preload?.video != null)
_PostVideoPlayer(data: data).padding(bottom: 8),
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
_PostFeaturedComment(data: data),
_PostBottomAction(
@@ -265,7 +273,8 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) {}
},
).padding(horizontal: 12, top: 8, bottom: 8),
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
if (data.preload?.video != null)
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
@@ -308,8 +317,13 @@ class PostItem extends StatelessWidget {
],
),
),
Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
Text('postArticle')
.tr()
.fontSize(13)
.opacity(0.75)
.padding(horizontal: 24, bottom: 8),
_PostFeaturedComment(data: data, maxWidth: maxWidth)
.padding(horizontal: 12),
_PostBottomAction(
data: data,
showComments: showComments,
@@ -324,7 +338,8 @@ class PostItem extends StatelessWidget {
}
final displayableAttachments = data.preload?.attachments
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
?.where((ele) =>
ele?.mediaType != SnMediaType.image || data.type != 'article')
.toList();
final cfg = context.read<ConfigProvider>();
@@ -349,9 +364,13 @@ class PostItem extends StatelessWidget {
if (onDeleted != null) onDeleted!();
},
).padding(horizontal: 12, vertical: 8),
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
if (data.body['title'] != null || data.body['description'] != null)
if (data.preload?.video != null)
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
if (data.type == 'question')
_PostQuestionHint(data: data)
.padding(horizontal: 16, bottom: 8),
if (data.body['title'] != null ||
data.body['description'] != null)
_PostHeadline(
data: data,
isEnlarge: data.type == 'article' && showFullPost,
@@ -365,7 +384,8 @@ class PostItem extends StatelessWidget {
if (data.repostTo != null)
_PostQuoteContent(child: data.repostTo!).padding(
horizontal: 12,
bottom: data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
bottom:
data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
),
if (data.visibility > 0)
_PostVisibilityHint(data: data).padding(
@@ -377,7 +397,9 @@ class PostItem extends StatelessWidget {
horizontal: 16,
vertical: 4,
),
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
if (data.tags.isNotEmpty)
_PostTagsList(data: data)
.padding(horizontal: 16, top: 4, bottom: 6),
],
),
),
@@ -390,12 +412,16 @@ class PostItem extends StatelessWidget {
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
padding: const EdgeInsets.symmetric(horizontal: 12),
),
if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
if (data.preload?.poll != null)
PostPoll(poll: data.preload!.poll!)
.padding(horizontal: 12, vertical: 4),
if (data.body['content'] != null &&
(cfg.prefs.getBool(kAppExpandPostLink) ?? true))
LinkPreviewWidget(
text: data.body['content'],
).padding(horizontal: 4),
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
_PostFeaturedComment(data: data, maxWidth: maxWidth)
.padding(horizontal: 12),
Container(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Column(
@@ -457,7 +483,8 @@ class PostShareImageWidget extends StatelessWidget {
showMenu: false,
isRelativeDate: false,
).padding(horizontal: 16, bottom: 8),
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
if (data.type == 'question')
_PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
_PostHeadline(
data: data,
isEnlarge: data.type == 'article',
@@ -472,7 +499,8 @@ class PostShareImageWidget extends StatelessWidget {
child: data.repostTo!,
isRelativeDate: false,
).padding(horizontal: 16, bottom: 8),
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
if (data.type != 'article' &&
(data.preload?.attachments?.isNotEmpty ?? false))
StyledWidget(AttachmentList(
data: data.preload!.attachments!,
columned: true,
@@ -481,7 +509,8 @@ class PostShareImageWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.visibility > 0) _PostVisibilityHint(data: data),
if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data),
if (data.body['content_truncated'] == true)
_PostTruncatedHint(data: data),
],
).padding(horizontal: 16),
_PostBottomAction(
@@ -541,7 +570,8 @@ class PostShareImageWidget extends StatelessWidget {
version: QrVersions.auto,
size: 100,
gapless: true,
embeddedImage: AssetImage('assets/icon/icon-light-radius.png'),
embeddedImage:
AssetImage('assets/icon/icon-light-radius.png'),
embeddedImageStyle: QrEmbeddedImageStyle(
size: Size(28, 28),
),
@@ -572,9 +602,11 @@ class _PostQuestionHint extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle,
size: 20),
const Gap(4),
if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
if (data.body['answer'] == null &&
data.body['reward']?.toDouble() != null)
Text('postQuestionUnansweredWithReward'.tr(args: [
'${data.body['reward']}',
])).opacity(0.75)
@@ -610,7 +642,9 @@ class _PostBottomAction extends StatelessWidget {
);
final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty
? data.metric.reactionList.entries.reduce((a, b) => a.value > b.value ? a : b).key
? data.metric.reactionList.entries
.reduce((a, b) => a.value > b.value ? a : b)
.key
: null;
return Row(
@@ -624,7 +658,8 @@ class _PostBottomAction extends StatelessWidget {
InkWell(
child: Row(
children: [
if (mostTypicalReaction == null || kTemplateReactions[mostTypicalReaction] == null)
if (mostTypicalReaction == null ||
kTemplateReactions[mostTypicalReaction] == null)
Icon(Symbols.add_reaction, size: 20, color: iconColor)
else
Text(
@@ -636,7 +671,8 @@ class _PostBottomAction extends StatelessWidget {
),
),
const Gap(8),
if (data.totalUpvote > 0 && data.totalUpvote >= data.totalDownvote)
if (data.totalUpvote > 0 &&
data.totalUpvote >= data.totalDownvote)
Text('postReactionUpvote').plural(
data.totalUpvote,
)
@@ -655,8 +691,12 @@ class _PostBottomAction extends StatelessWidget {
data: data,
onChanged: (value, attr, delta) {
onChanged(data.copyWith(
totalUpvote: attr == 1 ? data.totalUpvote + delta : data.totalUpvote,
totalDownvote: attr == 2 ? data.totalDownvote + delta : data.totalDownvote,
totalUpvote: attr == 1
? data.totalUpvote + delta
: data.totalUpvote,
totalDownvote: attr == 2
? data.totalDownvote + delta
: data.totalDownvote,
metric: data.metric.copyWith(reactionList: value),
));
},
@@ -904,8 +944,10 @@ class _PostContentHeader extends StatelessWidget {
const Gap(4),
Text(
isRelativeDate
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
? RelativeTime(context)
.format(data.publishedAt ?? data.createdAt)
: DateFormat('y/M/d HH:mm')
.format(data.publishedAt ?? data.createdAt),
).fontSize(13),
],
).opacity(0.8),
@@ -923,8 +965,10 @@ class _PostContentHeader extends StatelessWidget {
const Gap(4),
Text(
isRelativeDate
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
? RelativeTime(context)
.format(data.publishedAt ?? data.createdAt)
: DateFormat('y/M/d HH:mm')
.format(data.publishedAt ?? data.createdAt),
).fontSize(13),
],
).opacity(0.8),
@@ -1107,7 +1151,8 @@ class _PostContentBody extends StatelessWidget {
if (data.body['content'] == null) return const SizedBox.shrink();
final content = MarkdownTextContent(
isAutoWarp: data.type == 'story',
isEnlargeSticker: true,
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
content: data.body['content'],
attachments: data.preload?.attachments,
@@ -1156,10 +1201,12 @@ class _PostQuoteContent extends StatelessWidget {
onDeleted: () {},
).padding(bottom: 4),
_PostContentBody(data: child),
if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4),
if (child.visibility > 0)
_PostVisibilityHint(data: child).padding(top: 4),
],
).padding(horizontal: 16),
if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false))
if (child.type != 'article' &&
(child.preload?.attachments?.isNotEmpty ?? false))
ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(8),
@@ -1310,7 +1357,9 @@ class _PostTruncatedHint extends StatelessWidget {
const Gap(4),
Text('postReadEstimate').tr(args: [
'${Duration(
seconds: (data.body['content_length'] as num).toDouble() * 60 ~/ kHumanReadSpeed,
seconds: (data.body['content_length'] as num).toDouble() *
60 ~/
kHumanReadSpeed,
).inSeconds}s',
]),
],
@@ -1349,7 +1398,8 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
// If this is a answered question, fetch the answer instead
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
final resp =
await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
_isAnswer = true;
setState(() => _featuredComment = SnPost.fromJson(resp.data));
return;
@@ -1357,9 +1407,11 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
'take': 1,
});
final resp = await sn.client.get(
'/cgi/co/posts/${widget.data.id}/replies/featured',
queryParameters: {
'take': 1,
});
setState(() => _featuredComment = SnPost.fromJson(resp.data[0]));
} catch (err) {
if (!mounted) return;
@@ -1388,7 +1440,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
width: double.infinity,
child: Material(
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
color: _isAnswer
? Colors.green.withOpacity(0.5)
: Theme.of(context).colorScheme.surfaceContainerHigh,
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(8)),
onTap: () {
@@ -1408,11 +1462,17 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(2),
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion,
size: 20),
const Gap(10),
Text(
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
_isAnswer
? 'postQuestionAnswerTitle'
: 'postFeaturedComment',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 15),
).tr(),
],
),
@@ -1550,7 +1610,8 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
}
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
setState(
() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
@@ -1573,11 +1634,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
children: [
const Icon(Symbols.book_4_spark, size: 24),
const Gap(16),
Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(),
Text('postGetInsightTitle',
style: Theme.of(context).textTheme.titleLarge)
.tr(),
],
).padding(horizontal: 20, top: 16, bottom: 12),
const Gap(4),
Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20),
Text('postGetInsightDescription',
style: Theme.of(context).textTheme.bodySmall)
.tr()
.padding(horizontal: 20),
const Gap(4),
if (_response == null)
Expanded(
@@ -1595,12 +1661,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
leading: const Icon(Symbols.info),
title: Text('aiThinkingProcess'.tr()),
tilePadding: const EdgeInsets.symmetric(horizontal: 20),
collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
collapsedBackgroundColor:
Theme.of(context).colorScheme.surfaceContainerHigh,
minTileHeight: 32,
children: [
SelectableText(
_thinkingProcess!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic),
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontStyle: FontStyle.italic),
).padding(horizontal: 20, vertical: 8),
],
).padding(vertical: 8),
@@ -1637,7 +1707,8 @@ class _PostVideoPlayer extends StatelessWidget {
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
child: AttachmentItem(
data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
),
),
);

View File

@@ -12,6 +12,7 @@
#include <flutter_udid/flutter_udid_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
#include <local_notifier/local_notifier_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
@@ -38,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin");
hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar);
g_autoptr(FlPluginRegistrar) local_notifier_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin");
local_notifier_plugin_register_with_registrar(local_notifier_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);

View File

@@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_udid
flutter_webrtc
hotkey_manager_linux
local_notifier
media_kit_libs_linux
media_kit_video
pasteboard

View File

@@ -21,6 +21,7 @@ import gal
import hotkey_manager_macos
import in_app_review
import livekit_client
import local_notifier
import media_kit_libs_macos_video
import media_kit_video
import package_info_plus
@@ -53,6 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))

View File

@@ -13,59 +13,59 @@ PODS:
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/Analytics (11.7.0):
- Firebase/Analytics (11.8.0):
- Firebase/Core
- Firebase/Core (11.7.0):
- Firebase/Core (11.8.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 11.7.0)
- Firebase/CoreOnly (11.7.0):
- FirebaseCore (~> 11.7.0)
- Firebase/Messaging (11.7.0):
- FirebaseAnalytics (~> 11.8.0)
- Firebase/CoreOnly (11.8.0):
- FirebaseCore (~> 11.8.0)
- Firebase/Messaging (11.8.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.7.0)
- firebase_analytics (11.4.2):
- Firebase/Analytics (= 11.7.0)
- FirebaseMessaging (~> 11.8.0)
- firebase_analytics (11.4.3):
- Firebase/Analytics (= 11.8.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.11.0):
- Firebase/CoreOnly (~> 11.7.0)
- firebase_core (3.12.0):
- Firebase/CoreOnly (~> 11.8.0)
- FlutterMacOS
- firebase_messaging (15.2.2):
- Firebase/CoreOnly (~> 11.7.0)
- Firebase/Messaging (~> 11.7.0)
- firebase_messaging (15.2.3):
- Firebase/CoreOnly (~> 11.8.0)
- Firebase/Messaging (~> 11.8.0)
- firebase_core
- FlutterMacOS
- FirebaseAnalytics (11.7.0):
- FirebaseAnalytics/AdIdSupport (= 11.7.0)
- FirebaseCore (~> 11.7.0)
- FirebaseAnalytics (11.8.0):
- FirebaseAnalytics/AdIdSupport (= 11.8.0)
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseAnalytics/AdIdSupport (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.7.0)
- GoogleAppMeasurement (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.7.0):
- FirebaseCoreInternal (~> 11.7.0)
- FirebaseCore (11.8.1):
- FirebaseCoreInternal (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.7.0):
- FirebaseCoreInternal (11.8.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseInstallations (11.8.0):
- FirebaseCore (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.7.0):
- FirebaseCore (~> 11.7.0)
- FirebaseMessaging (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@@ -86,21 +86,21 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (11.7.0):
- GoogleAppMeasurement/AdIdSupport (= 11.7.0)
- GoogleAppMeasurement (11.8.0):
- GoogleAppMeasurement/AdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.7.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0)
- GoogleAppMeasurement/AdIdSupport (11.8.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0):
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
@@ -177,6 +177,25 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.49.1):
- sqlite3/common (= 3.49.1)
- sqlite3/common (3.49.1)
- sqlite3/dbstatvtab (3.49.1):
- sqlite3/common
- sqlite3/fts5 (3.49.1):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.1):
- sqlite3/common
- sqlite3/rtree (3.49.1):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.49.0)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
- tray_manager (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
@@ -216,6 +235,7 @@ DEPENDENCIES:
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
@@ -237,6 +257,7 @@ SPEC REPOS:
- OrderedSet
- PromisesObjC
- SAMKeychain
- sqlite3
- WebRTC-SDK
EXTERNAL SOURCES:
@@ -296,6 +317,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
sqlite3_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos:
@@ -313,21 +336,21 @@ SPEC CHECKSUMS:
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4
firebase_analytics: 41d88c024a7756462a803e36236ba74f24cdc2c5
firebase_core: 751d3d919b95d4ae46ab049d0d64d42d4eec086b
firebase_messaging: cc174f19945e9541e140e3cb0118448e59b38c6c
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_analytics: 1a71372a9735d7046d2c69db848a8d178f9fb587
firebase_core: 68e1d27035b096239f147a041643e14e156f1481
firebase_messaging: 89b5e0e28413dd878a58d2f286cdc03887b5d467
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
@@ -348,6 +371,8 @@ SPEC CHECKSUMS:
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f

View File

@@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -5,31 +5,26 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
url: "https://pub.dev"
source: hosted
version: "76.0.0"
version: "80.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32"
url: "https://pub.dev"
source: hosted
version: "1.3.51"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
version: "1.3.52"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
url: "https://pub.dev"
source: hosted
version: "6.11.0"
version: "7.3.0"
animations:
dependency: "direct main"
description:
@@ -50,10 +45,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.0.3"
args:
dependency: transitive
description:
@@ -66,10 +61,10 @@ packages:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev"
source: hosted
version: "2.11.0"
version: "2.12.0"
bitsdojo_window:
dependency: "direct main"
description:
@@ -114,10 +109,10 @@ packages:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
build:
dependency: transitive
description:
@@ -226,10 +221,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
charcode:
dependency: transitive
description:
@@ -258,10 +253,10 @@ packages:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
code_builder:
dependency: transitive
description:
@@ -274,10 +269,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.19.1"
connectivity_plus:
dependency: transitive
description:
@@ -354,18 +349,18 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
url: "https://pub.dev"
source: hosted
version: "2.3.8"
version: "3.0.1"
dart_webrtc:
dependency: "direct main"
description:
name: dart_webrtc
sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc"
sha256: b34e90bc82f33c1023cf98661369c37bccd648c8a4cf882a875d9f5d8bbef694
url: "https://pub.dev"
source: hosted
version: "1.5.1"
version: "1.5.2+hotfix.1"
dbus:
dependency: transitive
description:
@@ -506,18 +501,18 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.2"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
file:
dependency: transitive
description:
@@ -530,10 +525,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c"
url: "https://pub.dev"
source: hosted
version: "8.3.7"
version: "9.0.0"
file_saver:
dependency: "direct main"
description:
@@ -578,34 +573,34 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4"
url: "https://pub.dev"
source: hosted
version: "11.4.2"
version: "11.4.3"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4
url: "https://pub.dev"
source: hosted
version: "4.3.2"
version: "4.3.3"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910"
url: "https://pub.dev"
source: hosted
version: "0.5.10+8"
version: "0.5.10+9"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8"
url: "https://pub.dev"
source: hosted
version: "3.11.0"
version: "3.12.0"
firebase_core_platform_interface:
dependency: transitive
description:
@@ -618,34 +613,34 @@ packages:
dependency: transitive
description:
name: firebase_core_web
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41
url: "https://pub.dev"
source: hosted
version: "2.20.0"
version: "2.21.0"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "3dee3b0cbfe719e64773cb7d1cad57c58b2235a8c136f5715fe733a54058c783"
sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15"
url: "https://pub.dev"
source: hosted
version: "15.2.2"
version: "15.2.3"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: e9ea726b9bb864fc6223bb66422bd9877b9973ae51967754a769b0d01e201c1e
sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292"
url: "https://pub.dev"
source: hosted
version: "4.6.2"
version: "4.6.3"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "5f7b40e8bf861a37f8b8196e347d8a919750421a45f0b45d1bb74e98fa72726e"
sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091
url: "https://pub.dev"
source: hosted
version: "3.10.2"
version: "3.10.3"
fixnum:
dependency: transitive
description:
@@ -812,10 +807,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_native_splash
sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7"
sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "2.4.5"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -870,18 +865,18 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a"
sha256: "6ea3a86d95b61cfe42d5715426d355b3cece6c88d0119de428d56f6c653811ce"
url: "https://pub.dev"
source: hosted
version: "0.12.9"
version: "0.12.11"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
url: "https://pub.dev"
source: hosted
version: "2.5.7"
version: "2.5.8"
freezed_annotation:
dependency: "direct main"
description:
@@ -946,30 +941,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hive:
dependency: "direct main"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
url: "https://pub.dev"
source: hosted
version: "1.1.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
home_widget:
dependency: "direct main"
description:
@@ -1062,10 +1033,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3"
url: "https://pub.dev"
source: hosted
version: "4.5.2"
version: "4.5.3"
image_picker:
dependency: "direct main"
description:
@@ -1078,10 +1049,10 @@ packages:
dependency: "direct main"
description:
name: image_picker_android
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a"
url: "https://pub.dev"
source: hosted
version: "0.8.12+20"
version: "0.8.12+21"
image_picker_for_web:
dependency: transitive
description:
@@ -1182,26 +1153,26 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a"
url: "https://pub.dev"
source: hosted
version: "6.9.0"
version: "6.9.4"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev"
source: hosted
version: "10.0.7"
version: "10.0.8"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.8"
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
@@ -1230,10 +1201,18 @@ packages:
dependency: "direct main"
description:
name: livekit_client
sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52"
sha256: "753bbf484c6b70f10f3dc1dc808dfe3755f472d80eb9682323cff07ad8e2609d"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.4.0"
local_notifier:
dependency: "direct main"
description:
name: local_notifier
sha256: f6cfc933c6fbc961f4e52b5c880f68e41b2d3cd29aad557cc654fd211093a025
url: "https://pub.dev"
source: hosted
version: "0.1.6"
logging:
dependency: transitive
description:
@@ -1242,14 +1221,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
markdown:
dependency: "direct main"
description:
@@ -1270,10 +1241,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
@@ -1374,10 +1345,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.16.0"
mime:
dependency: "direct main"
description:
@@ -1446,10 +1417,10 @@ packages:
dependency: "direct main"
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
path_parsing:
dependency: transitive
description:
@@ -1518,26 +1489,26 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
version: "11.4.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev"
source: hosted
version: "12.0.13"
version: "12.1.0"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98
url: "https://pub.dev"
source: hosted
version: "9.4.5"
version: "9.4.6"
permission_handler_html:
dependency: transitive
description:
@@ -1550,10 +1521,10 @@ packages:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.2.3"
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
@@ -1566,10 +1537,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.0.2"
version: "6.1.0"
photo_view:
dependency: "direct main"
description:
@@ -1586,14 +1557,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.6"
platform_detect:
dependency: transitive
description:
name: platform_detect
sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f
url: "https://pub.dev"
source: hosted
version: "2.1.0"
plugin_platform_interface:
dependency: transitive
description:
@@ -1814,10 +1777,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22
url: "https://pub.dev"
source: hosted
version: "2.4.5"
version: "2.4.6"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1846,10 +1809,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
@@ -1907,10 +1870,10 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "2.0.0"
source_helper:
dependency: transitive
description:
@@ -1923,10 +1886,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.10.1"
sprintf:
dependency: transitive
description:
@@ -1939,34 +1902,34 @@ packages:
dependency: transitive
description:
name: sqflite
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
url: "https://pub.dev"
source: hosted
version: "2.5.4+6"
version: "2.5.5"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.dev"
source: hosted
version: "2.4.1+1"
version: "2.4.2"
sqflite_platform_interface:
dependency: transitive
description:
@@ -2003,18 +1966,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
stream_transform:
dependency: transitive
description:
@@ -2027,10 +1990,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.1"
styled_widget:
dependency: "direct main"
description:
@@ -2051,26 +2014,26 @@ packages:
dependency: "direct main"
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
version: "3.3.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.3"
version: "0.7.4"
timing:
dependency: transitive
description:
@@ -2259,10 +2222,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev"
source: hosted
version: "14.3.0"
version: "14.3.1"
volume_controller:
dependency: transitive
description:
@@ -2323,18 +2286,18 @@ packages:
dependency: transitive
description:
name: webrtc_interface
sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26"
sha256: e05f00091c9c70a15bab4ccb1b6c46d9a16a6075002f02cfac3641eccb05e25d
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.1+hotfix.1"
win32:
dependency: transitive
description:
name: win32
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef
url: "https://pub.dev"
source: hosted
version: "5.10.1"
version: "5.11.0"
win32_registry:
dependency: transitive
description:
@@ -2376,5 +2339,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.6.0 <4.0.0"
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0"

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
# 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.
version: 2.3.2+70
version: 2.3.2+71
environment:
sdk: ^3.5.4
@@ -59,7 +59,7 @@ dependencies:
relative_time: ^5.0.0
image_picker: ^1.1.2
cross_file: ^0.3.4+2
file_picker: ^8.1.6 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
file_picker: ^9.0.0 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
croppy: ^1.3.1
flutter_expandable_fab: ^2.3.0
dropdown_button2: ^2.3.9
@@ -72,8 +72,6 @@ dependencies:
collection: ^1.18.0
mime: ^2.0.0
web_socket_channel: ^3.0.1
hive: ^2.2.3
hive_flutter: ^1.1.0
swipe_to: ^1.0.6
firebase_core: ^3.8.0
firebase_messaging: ^15.1.5
@@ -125,6 +123,7 @@ dependencies:
image_picker_platform_interface: ^2.10.1
drift: ^2.25.1
drift_flutter: ^0.2.4
local_notifier: ^0.1.6
dev_dependencies:
flutter_test:
@@ -141,7 +140,6 @@ dev_dependencies:
json_serializable: ^6.8.0
icons_launcher: ^3.0.0
flutter_native_splash: ^2.4.2
hive_generator: ^2.0.1
flutter_launcher_icons: ^0.14.1
drift_dev: ^2.25.2

View File

@@ -17,6 +17,7 @@
#include <gal/gal_plugin_c_api.h>
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
#include <livekit_client/live_kit_plugin.h>
#include <local_notifier/local_notifier_plugin.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
@@ -50,6 +51,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
LiveKitPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LiveKitPlugin"));
LocalNotifierPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
MediaKitVideoPluginCApiRegisterWithRegistrar(

View File

@@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
gal
hotkey_manager_windows
livekit_client
local_notifier
media_kit_libs_windows_video
media_kit_video
pasteboard