Compare commits

...

21 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
3c0e4046a4 ♻️ Refactor to replace Hive with Sqlite 2025-02-22 20:43:24 +08:00
338c22a606 Add sqlite3 dependency 2025-02-22 16:22:33 +08:00
25dd895e0d 💄 Optimize category selector 2025-02-22 14:58:20 +08:00
ea9ef9e82a ♻️ Refactored explore page 2025-02-22 14:52:58 +08:00
52 changed files with 17605 additions and 1500 deletions

View File

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

View File

@@ -584,6 +584,7 @@
"colorSchemeBlack": "Black", "colorSchemeBlack": "Black",
"colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.", "colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.",
"postFeaturedComment": "Featured Comment", "postFeaturedComment": "Featured Comment",
"postCategory": "Category",
"postCategoryTechnology": "Technology", "postCategoryTechnology": "Technology",
"postCategoryGaming": "Gaming", "postCategoryGaming": "Gaming",
"postCategoryLife": "Life", "postCategoryLife": "Life",
@@ -675,5 +676,47 @@
"postThumbnail": "Post Thumbnail", "postThumbnail": "Post Thumbnail",
"accountRealms": "Realms", "accountRealms": "Realms",
"postInGlobal": "Global", "postInGlobal": "Global",
"postInGlobalDescription": "Do not link this post with any realm." "postInGlobalDescription": "Do not link this post with any realm.",
"postChannelGlobal": "Global",
"postChannelFriends": "Friends",
"postChannelFollowing": "Following",
"postChannelRealm": "Realms",
"postFilterReset": "Reset Filter",
"postFilterResetDescription": "Clear filter and show all posts.",
"postFilterWithCategory": "Viewing posts in {}",
"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.",
"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

@@ -582,6 +582,7 @@
"colorSchemeBlack": "黑色", "colorSchemeBlack": "黑色",
"colorSchemeApplied": "主题色已应用,可能需要重启来生效。", "colorSchemeApplied": "主题色已应用,可能需要重启来生效。",
"postFeaturedComment": "精选评论", "postFeaturedComment": "精选评论",
"postCategory": "分类",
"postCategoryTechnology": "技术", "postCategoryTechnology": "技术",
"postCategoryGaming": "游戏", "postCategoryGaming": "游戏",
"postCategoryLife": "生活", "postCategoryLife": "生活",
@@ -673,5 +674,47 @@
"postThumbnail": "帖子缩略图", "postThumbnail": "帖子缩略图",
"accountRealms": "领域", "accountRealms": "领域",
"postInGlobal": "全站", "postInGlobal": "全站",
"postInGlobalDescription": "不关联此帖子与任何领域。" "postInGlobalDescription": "不关联此帖子与任何领域。",
"postChannelGlobal": "全站",
"postChannelFriends": "好友",
"postChannelFollowing": "关注",
"postChannelRealm": "领域",
"postFilterReset": "重置过滤器",
"postFilterResetDescription": "清除过滤器并显示所有帖子。",
"postFilterWithCategory": "查看{}区中的帖子",
"databaseSize": "数据库大小",
"databaseDelete": "删除数据库",
"databaseDeleteDescription": "删除本地数据库,内容将从服务器重新获取。",
"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

@@ -582,6 +582,7 @@
"colorSchemeBlack": "黑色", "colorSchemeBlack": "黑色",
"colorSchemeApplied": "主題色已應用,可能需要重啓來生效。", "colorSchemeApplied": "主題色已應用,可能需要重啓來生效。",
"postFeaturedComment": "精選評論", "postFeaturedComment": "精選評論",
"postCategory": "分類",
"postCategoryTechnology": "技術", "postCategoryTechnology": "技術",
"postCategoryGaming": "遊戲", "postCategoryGaming": "遊戲",
"postCategoryLife": "生活", "postCategoryLife": "生活",
@@ -625,6 +626,7 @@
"realmJoin": "加入領域", "realmJoin": "加入領域",
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道", "realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
"realmCommunityPublishersHint": "該領域的發佈者",
"realmJoined": "已加入領域 {}。", "realmJoined": "已加入領域 {}。",
"join": "加入", "join": "加入",
"pollEditorNew": "新投票", "pollEditorNew": "新投票",
@@ -669,5 +671,49 @@
"attachmentBillingUploaded": "已佔用的字節數", "attachmentBillingUploaded": "已佔用的字節數",
"attachmentBillingDiscount": "免費的字節數", "attachmentBillingDiscount": "免費的字節數",
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。", "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
"postThumbnail": "帖子縮略圖" "postThumbnail": "帖子縮略圖",
"accountRealms": "領域",
"postInGlobal": "全站",
"postInGlobalDescription": "不關聯此帖子與任何領域。",
"postChannelGlobal": "全站",
"postChannelFriends": "好友",
"postChannelFollowing": "關注",
"postChannelRealm": "領域",
"postFilterReset": "重置過濾器",
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
"postFilterWithCategory": "查看{}區中的帖子",
"databaseSize": "數據庫大小",
"databaseDelete": "刪除數據庫",
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
"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

@@ -582,6 +582,7 @@
"colorSchemeBlack": "黑色", "colorSchemeBlack": "黑色",
"colorSchemeApplied": "主題色已應用,可能需要重啟來生效。", "colorSchemeApplied": "主題色已應用,可能需要重啟來生效。",
"postFeaturedComment": "精選評論", "postFeaturedComment": "精選評論",
"postCategory": "分類",
"postCategoryTechnology": "技術", "postCategoryTechnology": "技術",
"postCategoryGaming": "遊戲", "postCategoryGaming": "遊戲",
"postCategoryLife": "生活", "postCategoryLife": "生活",
@@ -625,6 +626,7 @@
"realmJoin": "加入領域", "realmJoin": "加入領域",
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。", "realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道", "realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
"realmCommunityPublishersHint": "該領域的發佈者",
"realmJoined": "已加入領域 {}。", "realmJoined": "已加入領域 {}。",
"join": "加入", "join": "加入",
"pollEditorNew": "新投票", "pollEditorNew": "新投票",
@@ -669,5 +671,49 @@
"attachmentBillingUploaded": "已佔用的字節數", "attachmentBillingUploaded": "已佔用的字節數",
"attachmentBillingDiscount": "免費的字節數", "attachmentBillingDiscount": "免費的字節數",
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。", "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。",
"postThumbnail": "帖子縮略圖" "postThumbnail": "帖子縮略圖",
"accountRealms": "領域",
"postInGlobal": "全站",
"postInGlobalDescription": "不關聯此帖子與任何領域。",
"postChannelGlobal": "全站",
"postChannelFriends": "好友",
"postChannelFollowing": "關注",
"postChannelRealm": "領域",
"postFilterReset": "重置過濾器",
"postFilterResetDescription": "清除過濾器並顯示所有帖子。",
"postFilterWithCategory": "查看{}區中的帖子",
"databaseSize": "數據庫大小",
"databaseDelete": "刪除數據庫",
"databaseDeleteDescription": "刪除本地數據庫,內容將從服務器重新獲取。",
"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 - Flutter
- file_saver (0.0.1): - file_saver (0.0.1):
- Flutter - Flutter
- Firebase/Analytics (11.7.0): - Firebase/Analytics (11.8.0):
- Firebase/Core - Firebase/Core
- Firebase/Core (11.7.0): - Firebase/Core (11.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseAnalytics (~> 11.7.0) - FirebaseAnalytics (~> 11.8.0)
- Firebase/CoreOnly (11.7.0): - Firebase/CoreOnly (11.8.0):
- FirebaseCore (~> 11.7.0) - FirebaseCore (~> 11.8.0)
- Firebase/Messaging (11.7.0): - Firebase/Messaging (11.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.7.0) - FirebaseMessaging (~> 11.8.0)
- firebase_analytics (11.4.2): - firebase_analytics (11.4.3):
- Firebase/Analytics (= 11.7.0) - Firebase/Analytics (= 11.8.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_core (3.11.0): - firebase_core (3.12.0):
- Firebase/CoreOnly (= 11.7.0) - Firebase/CoreOnly (= 11.8.0)
- Flutter - Flutter
- firebase_messaging (15.2.2): - firebase_messaging (15.2.3):
- Firebase/Messaging (= 11.7.0) - Firebase/Messaging (= 11.8.0)
- firebase_core - firebase_core
- Flutter - Flutter
- FirebaseAnalytics (11.7.0): - FirebaseAnalytics (11.8.0):
- FirebaseAnalytics/AdIdSupport (= 11.7.0) - FirebaseAnalytics/AdIdSupport (= 11.8.0)
- FirebaseCore (~> 11.7.0) - FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.7.0): - FirebaseAnalytics/AdIdSupport (11.8.0):
- FirebaseCore (~> 11.7.0) - FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.7.0) - GoogleAppMeasurement (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseCore (11.7.0): - FirebaseCore (11.8.1):
- FirebaseCoreInternal (~> 11.7.0) - FirebaseCoreInternal (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.7.0): - FirebaseCoreInternal (11.8.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.7.0): - FirebaseInstallations (11.8.0):
- FirebaseCore (~> 11.7.0) - FirebaseCore (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- FirebaseMessaging (11.7.0): - FirebaseMessaging (11.8.0):
- FirebaseCore (~> 11.7.0) - FirebaseCore (~> 11.8.0)
- FirebaseInstallations (~> 11.0) - FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0) - GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@@ -122,21 +122,21 @@ PODS:
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- GoogleAppMeasurement (11.7.0): - GoogleAppMeasurement (11.8.0):
- GoogleAppMeasurement/AdIdSupport (= 11.7.0) - GoogleAppMeasurement/AdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.7.0): - GoogleAppMeasurement/AdIdSupport (11.8.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.7.0) - GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)" - "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.7.0): - GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0) - GoogleUtilities/Network (~> 8.0)
@@ -210,9 +210,9 @@ PODS:
- SAMKeychain (1.5.3) - SAMKeychain (1.5.3)
- screen_brightness_ios (0.1.0): - screen_brightness_ios (0.1.0):
- Flutter - Flutter
- SDWebImage (5.20.0): - SDWebImage (5.20.1):
- SDWebImage/Core (= 5.20.0) - SDWebImage/Core (= 5.20.1)
- SDWebImage/Core (5.20.0) - SDWebImage/Core (5.20.1)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@@ -221,6 +221,25 @@ PODS:
- sqflite_darwin (0.0.4): - sqflite_darwin (0.0.4):
- Flutter - Flutter
- FlutterMacOS - 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
- SwiftyGif (5.4.5) - SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
@@ -268,6 +287,7 @@ DEPENDENCIES:
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_compress (from `.symlinks/plugins/video_compress/ios`) - video_compress (from `.symlinks/plugins/video_compress/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`) - volume_controller (from `.symlinks/plugins/volume_controller/ios`)
@@ -294,6 +314,7 @@ SPEC REPOS:
- PromisesObjC - PromisesObjC
- SAMKeychain - SAMKeychain
- SDWebImage - SDWebImage
- sqlite3
- SwiftyGif - SwiftyGif
- WebRTC-SDK - WebRTC-SDK
@@ -360,6 +381,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin: sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin" :path: ".symlinks/plugins/sqflite_darwin/darwin"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
video_compress: video_compress:
@@ -380,23 +403,23 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: a64bf6a8546e6eab54f1c715cd6151f39d2329f4 Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_analytics: 7236e6115c1b4e62c2270faa29c052a317e31107 firebase_analytics: 7ec1166af61987fa968766eb11587c562a5650ee
firebase_core: aa979ae726f00b3ef4ccf59dfb96170af84efbd4 firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682
firebase_messaging: 3af84b6a90aeac4d7a67fbf4c43a91e7083bea1f firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d
FirebaseAnalytics: bc9e565af9044ba8d6c6e4157e4edca8e5fdf7ec FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
FirebaseCore: 3227e35f4197a924206fbcdc0349325baf4f5de4 FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: d6c17dafc8dc33614733a8b52df78fcb4394c881 FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 9347e719c3d52d8d7b9074b2c32407dd027305e9 FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 00ece041b71ddb52a2862ffdee73fb6e9824bd0c FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1 flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5 gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
GoogleAppMeasurement: 0471a5b5bff51f3a91b1e76df22c952d04c63967 GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
@@ -417,10 +440,12 @@ SPEC CHECKSUMS:
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1 receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 SDWebImage: 33d0f23bddeb5d209ae959153883247be6703713
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe

View File

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

View File

@@ -1,12 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/database/database.dart';
import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
@@ -16,13 +18,13 @@ import 'package:surface/types/websocket.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChatMessageController extends ChangeNotifier { class ChatMessageController extends ChangeNotifier {
static const kChatMessageBoxPrefix = 'nex_chat_messages_';
static const kSingleBatchLoadLimit = 100; static const kSingleBatchLoadLimit = 100;
late final SnNetworkProvider _sn; late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud; late final UserDirectoryProvider _ud;
late final WebSocketProvider _ws; late final WebSocketProvider _ws;
late final SnAttachmentProvider _attach; late final SnAttachmentProvider _attach;
late final DatabaseProvider _dt;
StreamSubscription? _wsSubscription; StreamSubscription? _wsSubscription;
@@ -31,6 +33,7 @@ class ChatMessageController extends ChangeNotifier {
_ud = context.read<UserDirectoryProvider>(); _ud = context.read<UserDirectoryProvider>();
_ws = context.read<WebSocketProvider>(); _ws = context.read<WebSocketProvider>();
_attach = context.read<SnAttachmentProvider>(); _attach = context.read<SnAttachmentProvider>();
_dt = context.read<DatabaseProvider>();
} }
bool isPending = true; bool isPending = true;
@@ -38,9 +41,9 @@ class ChatMessageController extends ChangeNotifier {
int? messageTotal; int? messageTotal;
bool get isAllLoaded => messageTotal != null && messages.length >= messageTotal!; bool get isAllLoaded =>
messageTotal != null && messages.length >= messageTotal!;
String? _boxKey;
SnChannel? channel; SnChannel? channel;
SnChannelMember? profile; SnChannelMember? profile;
@@ -51,25 +54,17 @@ class ChatMessageController extends ChangeNotifier {
/// Stored as a list of nonce to provide the loading state /// Stored as a list of nonce to provide the loading state
final List<String> unconfirmedMessages = List.empty(growable: true); final List<String> unconfirmedMessages = List.empty(growable: true);
Box<SnChatMessage>? get _box => (_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!);
final List<SnChannelMember> typingMembers = List.empty(growable: true); final List<SnChannelMember> typingMembers = List.empty(growable: true);
final Map<int, Timer> typingInactiveTimer = {}; final Map<int, Timer> typingInactiveTimer = {};
Future<void> initialize(SnChannel chan) async { Future<void> initialize(SnChannel chan) async {
channel = chan; channel = chan;
// Initialize local data
_boxKey = '$kChatMessageBoxPrefix${chan.id}';
await Hive.openBox<SnChatMessage>(_boxKey!);
// Fetch channel profile // Fetch channel profile
final resp = await _sn.client.get( final resp = await _sn.client.get(
'/cgi/im/channels/${chan.keyPath}/me', '/cgi/im/channels/${chan.keyPath}/me',
); );
profile = SnChannelMember.fromJson( profile = SnChannelMember.fromJson(resp.data);
resp.data as Map<String, dynamic>,
);
_wsSubscription = _ws.pk.stream.listen((event) { _wsSubscription = _ws.pk.stream.listen((event) {
switch (event.method) { switch (event.method) {
@@ -87,7 +82,8 @@ class ChatMessageController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
typingInactiveTimer[member.id]?.cancel(); typingInactiveTimer[member.id]?.cancel();
typingInactiveTimer[member.id] = Timer(const Duration(seconds: 3), () { typingInactiveTimer[member.id] =
Timer(const Duration(seconds: 3), () {
typingMembers.removeWhere((x) => x.id == member.id); typingMembers.removeWhere((x) => x.id == member.id);
typingInactiveTimer.remove(member.id); typingInactiveTimer.remove(member.id);
notifyListeners(); notifyListeners();
@@ -129,10 +125,16 @@ class ChatMessageController extends ChangeNotifier {
} }
Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async { Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async {
if (_box == null) return; await _dt.db.snLocalChatMessage.insertAll(
await _box!.putAll({ messages.map(
for (final message in messages) message.id: message, (ele) => SnLocalChatMessageCompanion.insert(
}); id: Value(ele.id),
content: ele,
channelId: channel!.id,
createdAt: Value(ele.createdAt),
),
),
onConflict: DoNothing());
} }
Future<void> _addUnconfirmedMessage(SnChatMessage message) async { Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
@@ -184,8 +186,21 @@ class ChatMessageController extends ChangeNotifier {
await _applyMessage(message); await _applyMessage(message);
notifyListeners(); notifyListeners();
if (_box == null) return; if (isCheckedUpdate) {
await _box!.put(message.id, message); await _dt.db.snLocalChatMessage.insertOne(
SnLocalChatMessageCompanion.insert(
id: Value(message.id),
content: message,
channelId: channel!.id,
createdAt: Value(message.createdAt),
),
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(message.toJson())),
)),
);
} else {
incomeStrandedQueue.add(message);
}
} }
Future<void> _applyMessage(SnChatMessage message) async { Future<void> _applyMessage(SnChatMessage message) async {
@@ -194,7 +209,8 @@ class ChatMessageController extends ChangeNotifier {
switch (message.type) { switch (message.type) {
case 'messages.edit': case 'messages.edit':
if (message.relatedEventId != null) { if (message.relatedEventId != null) {
final idx = messages.indexWhere((x) => x.id == message.relatedEventId); final idx =
messages.indexWhere((x) => x.id == message.relatedEventId);
if (idx != -1) { if (idx != -1) {
final newBody = message.body; final newBody = message.body;
newBody.remove('related_event'); newBody.remove('related_event');
@@ -202,16 +218,24 @@ class ChatMessageController extends ChangeNotifier {
body: newBody, body: newBody,
updatedAt: message.updatedAt, updatedAt: message.updatedAt,
); );
if (_box!.containsKey(message.relatedEventId)) { if (message.relatedEventId != null) {
await _box!.put(message.relatedEventId, messages[idx]); await (_dt.db.snLocalChatMessage.update()
..where((e) => e.id.equals(message.relatedEventId!)))
.write(
SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(messages[idx].toJson())),
),
);
} }
} }
} }
case 'messages.delete': case 'messages.delete':
if (message.relatedEventId != null) { if (message.relatedEventId != null) {
messages.removeWhere((x) => x.id == message.relatedEventId); messages.removeWhere((x) => x.id == message.relatedEventId);
if (_box!.containsKey(message.relatedEventId)) { if (message.relatedEventId != null) {
await _box!.delete(message.relatedEventId); await (_dt.db.snLocalChatMessage.delete()
..where((e) => e.id.equals(message.relatedEventId!)))
.go();
} }
} }
} }
@@ -233,7 +257,8 @@ class ChatMessageController extends ChangeNotifier {
'algorithm': 'plain', 'algorithm': 'plain',
if (quoteId != null) 'quote_event': quoteId, if (quoteId != null) 'quote_event': quoteId,
if (relatedId != null) 'related_event': relatedId, if (relatedId != null) 'related_event': relatedId,
if (attachments != null && attachments.isNotEmpty) 'attachments': attachments, if (attachments != null && attachments.isNotEmpty)
'attachments': attachments,
}; };
// Mock the message locally // Mock the message locally
@@ -287,20 +312,34 @@ class ChatMessageController extends ChangeNotifier {
} }
} }
bool isCheckedUpdate = false;
List<SnChatMessage> incomeStrandedQueue = List.empty(growable: true);
/// Check the local storage is up to date with the server. /// Check the local storage is up to date with the server.
/// If the local storage is not up to date, it will be updated. /// If the local storage is not up to date, it will be updated.
Future<void> checkUpdate() async { Future<void> checkUpdate() async {
if (_box == null) return;
if (_box!.isEmpty) return;
isLoading = true; isLoading = true;
notifyListeners(); notifyListeners();
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
..limit(1)
..orderBy([
(e) =>
OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
]))
.getSingleOrNull();
if (mostRecentMessage == null) {
// Initial load
await loadMessages(take: 20);
isCheckedUpdate = true;
return;
}
try { try {
final resp = await _sn.client.get( final resp = await _sn.client.get(
'/cgi/im/channels/${channel!.keyPath}/events/update', '/cgi/im/channels/${channel!.keyPath}/events/update',
queryParameters: { queryParameters: {
'pivot': _box!.values.last.id, 'pivot': mostRecentMessage.content.id,
}, },
); );
if (resp.data['up_to_date'] == true) return; if (resp.data['up_to_date'] == true) return;
@@ -316,6 +355,12 @@ class ChatMessageController extends ChangeNotifier {
} finally { } finally {
await loadMessages(); await loadMessages();
isLoading = false; isLoading = false;
isCheckedUpdate = true;
_saveMessageToLocal(incomeStrandedQueue).then((_) {
incomeStrandedQueue.clear();
});
notifyListeners(); notifyListeners();
} }
} }
@@ -324,13 +369,18 @@ class ChatMessageController extends ChangeNotifier {
/// If it was not found in local storage we will look it up in remote /// If it was not found in local storage we will look it up in remote
Future<SnChatMessage?> getMessage(int id) async { Future<SnChatMessage?> getMessage(int id) async {
SnChatMessage? out; SnChatMessage? out;
if (_box != null && _box!.containsKey(id)) { final local = await (_dt.db.snLocalChatMessage.select()
out = _box!.get(id); ..limit(1)
..where((e) => e.id.equals(id)))
.getSingleOrNull();
if (local != null) {
out = local.content;
} }
if (out == null) { if (out == null) {
try { try {
final resp = await _sn.client.get('/cgi/im/channels/${channel!.keyPath}/events/$id'); final resp = await _sn.client
.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
out = SnChatMessage.fromJson(resp.data); out = SnChatMessage.fromJson(resp.data);
_saveMessageToLocal([out]); _saveMessageToLocal([out]);
} catch (_) { } catch (_) {
@@ -364,16 +414,21 @@ class ChatMessageController extends ChangeNotifier {
bool forceLocal = false, bool forceLocal = false,
bool forceRemote = false, bool forceRemote = false,
}) async { }) async {
final localTotal = await _dt.db.snLocalChatMessage
.count(where: (e) => e.channelId.equals(channel!.id))
.getSingle();
late List<SnChatMessage> out; late List<SnChatMessage> out;
if (_box != null && (_box!.length >= take + offset || forceLocal) && !forceRemote) { if ((localTotal >= take + offset || forceLocal) && !forceRemote) {
out = _box!.keys final result = await (_dt.db.snLocalChatMessage.select()
.toList() ..where((e) => e.channelId.equals(channel!.id))
.cast<int>() ..orderBy([
.sorted((a, b) => b.compareTo(a)) (e) =>
.skip(offset) OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
.take(take) ])
.map((key) => _box!.get(key)!) ..limit(take, offset: offset))
.toList(); .get();
out = result.map((e) => e.content).toList();
} else { } else {
final resp = await _sn.client.get( final resp = await _sn.client.get(
'/cgi/im/channels/${channel!.keyPath}/events', '/cgi/im/channels/${channel!.keyPath}/events',
@@ -408,7 +463,8 @@ class ChatMessageController extends ChangeNotifier {
quoteEvent: quoteEvent, quoteEvent: quoteEvent,
attachments: attachments attachments: attachments
.where( .where(
(ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false, (ele) =>
out[i].body['attachments']?.contains(ele?.rid) ?? false,
) )
.toList(), .toList(),
), ),
@@ -416,7 +472,10 @@ class ChatMessageController extends ChangeNotifier {
} }
// Preload sender accounts // Preload sender accounts
final accountId = out.where((ele) => ele.sender.accountId >= 0).map((ele) => ele.sender.accountId).toSet(); final accountId = out
.where((ele) => ele.sender.accountId >= 0)
.map((ele) => ele.sender.accountId)
.toSet();
await _ud.listAccount(accountId); await _ud.listAccount(accountId);
return out; return out;
@@ -441,10 +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 @override
void dispose() { void dispose() {
_box?.close();
_wsSubscription?.cancel(); _wsSubscription?.cancel();
if (_readEventDebounce?.isActive ?? false) {
_sendReadEvent();
}
_readEventDebounce?.cancel();
super.dispose(); super.dispose();
} }
} }

74
lib/database/chat.dart Normal file
View File

@@ -0,0 +1,74 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:surface/types/chat.dart';
class SnChannelConverter extends TypeConverter<SnChannel, String>
with JsonTypeConverter2<SnChannel, String, Map<String, Object?>> {
const SnChannelConverter();
@override
SnChannel fromSql(String fromDb) {
return fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
}
@override
String toSql(SnChannel value) {
return jsonEncode(toJson(value));
}
@override
SnChannel fromJson(Map<String, Object?> json) {
return SnChannel.fromJson(json);
}
@override
Map<String, Object?> toJson(SnChannel value) {
return value.toJson();
}
}
class SnLocalChatChannel extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get alias => text()();
TextColumn get content => text().map(const SnChannelConverter())();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}
class SnMessageConverter extends TypeConverter<SnChatMessage, String>
with JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>> {
const SnMessageConverter();
@override
SnChatMessage fromSql(String fromDb) {
return fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
}
@override
String toSql(SnChatMessage value) {
return jsonEncode(toJson(value));
}
@override
SnChatMessage fromJson(Map<String, Object?> json) {
return SnChatMessage.fromJson(json);
}
@override
Map<String, Object?> toJson(SnChatMessage value) {
return value.toJson();
}
}
class SnLocalChatMessage extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get channelId => integer()();
TextColumn get content => text().map(const SnMessageConverter())();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

View File

@@ -0,0 +1,28 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:surface/database/chat.dart';
import 'package:surface/types/chat.dart';
part 'database.g.dart';
@DriftDatabase(tables: [SnLocalChatChannel, SnLocalChatMessage])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'solar_network_data',
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
web: DriftWebOptions(
sqlite3Wasm: Uri.parse('sqlite3.wasm'),
driftWorker: Uri.parse('drift_worker.dart.js'),
),
);
}
}

View File

@@ -0,0 +1,880 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// ignore_for_file: type=lint
class $SnLocalChatChannelTable extends SnLocalChatChannel
with TableInfo<$SnLocalChatChannelTable, SnLocalChatChannelData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$SnLocalChatChannelTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const VerificationMeta _aliasMeta = const VerificationMeta('alias');
@override
late final GeneratedColumn<String> alias = GeneratedColumn<String>(
'alias', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override
late final GeneratedColumnWithTypeConverter<SnChannel, String> content =
GeneratedColumn<String>('content', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<SnChannel>($SnLocalChatChannelTable.$convertercontent);
static const VerificationMeta _createdAtMeta =
const VerificationMeta('createdAt');
@override
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
'created_at', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
@override
List<GeneratedColumn> get $columns => [id, alias, content, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'sn_local_chat_channel';
@override
VerificationContext validateIntegrity(
Insertable<SnLocalChatChannelData> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('alias')) {
context.handle(
_aliasMeta, alias.isAcceptableOrUnknown(data['alias']!, _aliasMeta));
} else if (isInserting) {
context.missing(_aliasMeta);
}
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
SnLocalChatChannelData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return SnLocalChatChannelData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
alias: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}alias'])!,
content: $SnLocalChatChannelTable.$convertercontent.fromSql(
attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}content'])!),
createdAt: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
);
}
@override
$SnLocalChatChannelTable createAlias(String alias) {
return $SnLocalChatChannelTable(attachedDatabase, alias);
}
static JsonTypeConverter2<SnChannel, String, Map<String, Object?>>
$convertercontent = const SnChannelConverter();
}
class SnLocalChatChannelData extends DataClass
implements Insertable<SnLocalChatChannelData> {
final int id;
final String alias;
final SnChannel content;
final DateTime createdAt;
const SnLocalChatChannelData(
{required this.id,
required this.alias,
required this.content,
required this.createdAt});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['alias'] = Variable<String>(alias);
{
map['content'] = Variable<String>(
$SnLocalChatChannelTable.$convertercontent.toSql(content));
}
map['created_at'] = Variable<DateTime>(createdAt);
return map;
}
SnLocalChatChannelCompanion toCompanion(bool nullToAbsent) {
return SnLocalChatChannelCompanion(
id: Value(id),
alias: Value(alias),
content: Value(content),
createdAt: Value(createdAt),
);
}
factory SnLocalChatChannelData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return SnLocalChatChannelData(
id: serializer.fromJson<int>(json['id']),
alias: serializer.fromJson<String>(json['alias']),
content: $SnLocalChatChannelTable.$convertercontent
.fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'alias': serializer.toJson<String>(alias),
'content': serializer.toJson<Map<String, Object?>>(
$SnLocalChatChannelTable.$convertercontent.toJson(content)),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
SnLocalChatChannelData copyWith(
{int? id, String? alias, SnChannel? content, DateTime? createdAt}) =>
SnLocalChatChannelData(
id: id ?? this.id,
alias: alias ?? this.alias,
content: content ?? this.content,
createdAt: createdAt ?? this.createdAt,
);
SnLocalChatChannelData copyWithCompanion(SnLocalChatChannelCompanion data) {
return SnLocalChatChannelData(
id: data.id.present ? data.id.value : this.id,
alias: data.alias.present ? data.alias.value : this.alias,
content: data.content.present ? data.content.value : this.content,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@override
String toString() {
return (StringBuffer('SnLocalChatChannelData(')
..write('id: $id, ')
..write('alias: $alias, ')
..write('content: $content, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, alias, content, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is SnLocalChatChannelData &&
other.id == this.id &&
other.alias == this.alias &&
other.content == this.content &&
other.createdAt == this.createdAt);
}
class SnLocalChatChannelCompanion
extends UpdateCompanion<SnLocalChatChannelData> {
final Value<int> id;
final Value<String> alias;
final Value<SnChannel> content;
final Value<DateTime> createdAt;
const SnLocalChatChannelCompanion({
this.id = const Value.absent(),
this.alias = const Value.absent(),
this.content = const Value.absent(),
this.createdAt = const Value.absent(),
});
SnLocalChatChannelCompanion.insert({
this.id = const Value.absent(),
required String alias,
required SnChannel content,
this.createdAt = const Value.absent(),
}) : alias = Value(alias),
content = Value(content);
static Insertable<SnLocalChatChannelData> custom({
Expression<int>? id,
Expression<String>? alias,
Expression<String>? content,
Expression<DateTime>? createdAt,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (alias != null) 'alias': alias,
if (content != null) 'content': content,
if (createdAt != null) 'created_at': createdAt,
});
}
SnLocalChatChannelCompanion copyWith(
{Value<int>? id,
Value<String>? alias,
Value<SnChannel>? content,
Value<DateTime>? createdAt}) {
return SnLocalChatChannelCompanion(
id: id ?? this.id,
alias: alias ?? this.alias,
content: content ?? this.content,
createdAt: createdAt ?? this.createdAt,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (alias.present) {
map['alias'] = Variable<String>(alias.value);
}
if (content.present) {
map['content'] = Variable<String>(
$SnLocalChatChannelTable.$convertercontent.toSql(content.value));
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('SnLocalChatChannelCompanion(')
..write('id: $id, ')
..write('alias: $alias, ')
..write('content: $content, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
}
class $SnLocalChatMessageTable extends SnLocalChatMessage
with TableInfo<$SnLocalChatMessageTable, SnLocalChatMessageData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$SnLocalChatMessageTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const VerificationMeta _channelIdMeta =
const VerificationMeta('channelId');
@override
late final GeneratedColumn<int> channelId = GeneratedColumn<int>(
'channel_id', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override
late final GeneratedColumnWithTypeConverter<SnChatMessage, String> content =
GeneratedColumn<String>('content', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<SnChatMessage>(
$SnLocalChatMessageTable.$convertercontent);
static const VerificationMeta _createdAtMeta =
const VerificationMeta('createdAt');
@override
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
'created_at', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
@override
List<GeneratedColumn> get $columns => [id, channelId, content, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'sn_local_chat_message';
@override
VerificationContext validateIntegrity(
Insertable<SnLocalChatMessageData> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('channel_id')) {
context.handle(_channelIdMeta,
channelId.isAcceptableOrUnknown(data['channel_id']!, _channelIdMeta));
} else if (isInserting) {
context.missing(_channelIdMeta);
}
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
SnLocalChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return SnLocalChatMessageData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
channelId: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}channel_id'])!,
content: $SnLocalChatMessageTable.$convertercontent.fromSql(
attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}content'])!),
createdAt: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
);
}
@override
$SnLocalChatMessageTable createAlias(String alias) {
return $SnLocalChatMessageTable(attachedDatabase, alias);
}
static JsonTypeConverter2<SnChatMessage, String, Map<String, Object?>>
$convertercontent = const SnMessageConverter();
}
class SnLocalChatMessageData extends DataClass
implements Insertable<SnLocalChatMessageData> {
final int id;
final int channelId;
final SnChatMessage content;
final DateTime createdAt;
const SnLocalChatMessageData(
{required this.id,
required this.channelId,
required this.content,
required this.createdAt});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['channel_id'] = Variable<int>(channelId);
{
map['content'] = Variable<String>(
$SnLocalChatMessageTable.$convertercontent.toSql(content));
}
map['created_at'] = Variable<DateTime>(createdAt);
return map;
}
SnLocalChatMessageCompanion toCompanion(bool nullToAbsent) {
return SnLocalChatMessageCompanion(
id: Value(id),
channelId: Value(channelId),
content: Value(content),
createdAt: Value(createdAt),
);
}
factory SnLocalChatMessageData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return SnLocalChatMessageData(
id: serializer.fromJson<int>(json['id']),
channelId: serializer.fromJson<int>(json['channelId']),
content: $SnLocalChatMessageTable.$convertercontent
.fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'channelId': serializer.toJson<int>(channelId),
'content': serializer.toJson<Map<String, Object?>>(
$SnLocalChatMessageTable.$convertercontent.toJson(content)),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
SnLocalChatMessageData copyWith(
{int? id,
int? channelId,
SnChatMessage? content,
DateTime? createdAt}) =>
SnLocalChatMessageData(
id: id ?? this.id,
channelId: channelId ?? this.channelId,
content: content ?? this.content,
createdAt: createdAt ?? this.createdAt,
);
SnLocalChatMessageData copyWithCompanion(SnLocalChatMessageCompanion data) {
return SnLocalChatMessageData(
id: data.id.present ? data.id.value : this.id,
channelId: data.channelId.present ? data.channelId.value : this.channelId,
content: data.content.present ? data.content.value : this.content,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@override
String toString() {
return (StringBuffer('SnLocalChatMessageData(')
..write('id: $id, ')
..write('channelId: $channelId, ')
..write('content: $content, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, channelId, content, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is SnLocalChatMessageData &&
other.id == this.id &&
other.channelId == this.channelId &&
other.content == this.content &&
other.createdAt == this.createdAt);
}
class SnLocalChatMessageCompanion
extends UpdateCompanion<SnLocalChatMessageData> {
final Value<int> id;
final Value<int> channelId;
final Value<SnChatMessage> content;
final Value<DateTime> createdAt;
const SnLocalChatMessageCompanion({
this.id = const Value.absent(),
this.channelId = const Value.absent(),
this.content = const Value.absent(),
this.createdAt = const Value.absent(),
});
SnLocalChatMessageCompanion.insert({
this.id = const Value.absent(),
required int channelId,
required SnChatMessage content,
this.createdAt = const Value.absent(),
}) : channelId = Value(channelId),
content = Value(content);
static Insertable<SnLocalChatMessageData> custom({
Expression<int>? id,
Expression<int>? channelId,
Expression<String>? content,
Expression<DateTime>? createdAt,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (channelId != null) 'channel_id': channelId,
if (content != null) 'content': content,
if (createdAt != null) 'created_at': createdAt,
});
}
SnLocalChatMessageCompanion copyWith(
{Value<int>? id,
Value<int>? channelId,
Value<SnChatMessage>? content,
Value<DateTime>? createdAt}) {
return SnLocalChatMessageCompanion(
id: id ?? this.id,
channelId: channelId ?? this.channelId,
content: content ?? this.content,
createdAt: createdAt ?? this.createdAt,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (channelId.present) {
map['channel_id'] = Variable<int>(channelId.value);
}
if (content.present) {
map['content'] = Variable<String>(
$SnLocalChatMessageTable.$convertercontent.toSql(content.value));
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('SnLocalChatMessageCompanion(')
..write('id: $id, ')
..write('channelId: $channelId, ')
..write('content: $content, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
}
abstract class _$AppDatabase extends GeneratedDatabase {
_$AppDatabase(QueryExecutor e) : super(e);
$AppDatabaseManager get managers => $AppDatabaseManager(this);
late final $SnLocalChatChannelTable snLocalChatChannel =
$SnLocalChatChannelTable(this);
late final $SnLocalChatMessageTable snLocalChatMessage =
$SnLocalChatMessageTable(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[snLocalChatChannel, snLocalChatMessage];
}
typedef $$SnLocalChatChannelTableCreateCompanionBuilder
= SnLocalChatChannelCompanion Function({
Value<int> id,
required String alias,
required SnChannel content,
Value<DateTime> createdAt,
});
typedef $$SnLocalChatChannelTableUpdateCompanionBuilder
= SnLocalChatChannelCompanion Function({
Value<int> id,
Value<String> alias,
Value<SnChannel> content,
Value<DateTime> createdAt,
});
class $$SnLocalChatChannelTableFilterComposer
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
$$SnLocalChatChannelTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get alias => $composableBuilder(
column: $table.alias, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<SnChannel, SnChannel, String> get content =>
$composableBuilder(
column: $table.content,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnFilters(column));
}
class $$SnLocalChatChannelTableOrderingComposer
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
$$SnLocalChatChannelTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get alias => $composableBuilder(
column: $table.alias, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get content => $composableBuilder(
column: $table.content, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
}
class $$SnLocalChatChannelTableAnnotationComposer
extends Composer<_$AppDatabase, $SnLocalChatChannelTable> {
$$SnLocalChatChannelTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get alias =>
$composableBuilder(column: $table.alias, builder: (column) => column);
GeneratedColumnWithTypeConverter<SnChannel, String> get content =>
$composableBuilder(column: $table.content, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
}
class $$SnLocalChatChannelTableTableManager extends RootTableManager<
_$AppDatabase,
$SnLocalChatChannelTable,
SnLocalChatChannelData,
$$SnLocalChatChannelTableFilterComposer,
$$SnLocalChatChannelTableOrderingComposer,
$$SnLocalChatChannelTableAnnotationComposer,
$$SnLocalChatChannelTableCreateCompanionBuilder,
$$SnLocalChatChannelTableUpdateCompanionBuilder,
(
SnLocalChatChannelData,
BaseReferences<_$AppDatabase, $SnLocalChatChannelTable,
SnLocalChatChannelData>
),
SnLocalChatChannelData,
PrefetchHooks Function()> {
$$SnLocalChatChannelTableTableManager(
_$AppDatabase db, $SnLocalChatChannelTable table)
: super(TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$SnLocalChatChannelTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$SnLocalChatChannelTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$SnLocalChatChannelTableAnnotationComposer(
$db: db, $table: table),
updateCompanionCallback: ({
Value<int> id = const Value.absent(),
Value<String> alias = const Value.absent(),
Value<SnChannel> content = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
SnLocalChatChannelCompanion(
id: id,
alias: alias,
content: content,
createdAt: createdAt,
),
createCompanionCallback: ({
Value<int> id = const Value.absent(),
required String alias,
required SnChannel content,
Value<DateTime> createdAt = const Value.absent(),
}) =>
SnLocalChatChannelCompanion.insert(
id: id,
alias: alias,
content: content,
createdAt: createdAt,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
));
}
typedef $$SnLocalChatChannelTableProcessedTableManager = ProcessedTableManager<
_$AppDatabase,
$SnLocalChatChannelTable,
SnLocalChatChannelData,
$$SnLocalChatChannelTableFilterComposer,
$$SnLocalChatChannelTableOrderingComposer,
$$SnLocalChatChannelTableAnnotationComposer,
$$SnLocalChatChannelTableCreateCompanionBuilder,
$$SnLocalChatChannelTableUpdateCompanionBuilder,
(
SnLocalChatChannelData,
BaseReferences<_$AppDatabase, $SnLocalChatChannelTable,
SnLocalChatChannelData>
),
SnLocalChatChannelData,
PrefetchHooks Function()>;
typedef $$SnLocalChatMessageTableCreateCompanionBuilder
= SnLocalChatMessageCompanion Function({
Value<int> id,
required int channelId,
required SnChatMessage content,
Value<DateTime> createdAt,
});
typedef $$SnLocalChatMessageTableUpdateCompanionBuilder
= SnLocalChatMessageCompanion Function({
Value<int> id,
Value<int> channelId,
Value<SnChatMessage> content,
Value<DateTime> createdAt,
});
class $$SnLocalChatMessageTableFilterComposer
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
$$SnLocalChatMessageTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<int> get channelId => $composableBuilder(
column: $table.channelId, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<SnChatMessage, SnChatMessage, String>
get content => $composableBuilder(
column: $table.content,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnFilters(column));
}
class $$SnLocalChatMessageTableOrderingComposer
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
$$SnLocalChatMessageTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get channelId => $composableBuilder(
column: $table.channelId, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get content => $composableBuilder(
column: $table.content, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
}
class $$SnLocalChatMessageTableAnnotationComposer
extends Composer<_$AppDatabase, $SnLocalChatMessageTable> {
$$SnLocalChatMessageTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<int> get channelId =>
$composableBuilder(column: $table.channelId, builder: (column) => column);
GeneratedColumnWithTypeConverter<SnChatMessage, String> get content =>
$composableBuilder(column: $table.content, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
}
class $$SnLocalChatMessageTableTableManager extends RootTableManager<
_$AppDatabase,
$SnLocalChatMessageTable,
SnLocalChatMessageData,
$$SnLocalChatMessageTableFilterComposer,
$$SnLocalChatMessageTableOrderingComposer,
$$SnLocalChatMessageTableAnnotationComposer,
$$SnLocalChatMessageTableCreateCompanionBuilder,
$$SnLocalChatMessageTableUpdateCompanionBuilder,
(
SnLocalChatMessageData,
BaseReferences<_$AppDatabase, $SnLocalChatMessageTable,
SnLocalChatMessageData>
),
SnLocalChatMessageData,
PrefetchHooks Function()> {
$$SnLocalChatMessageTableTableManager(
_$AppDatabase db, $SnLocalChatMessageTable table)
: super(TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$SnLocalChatMessageTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$SnLocalChatMessageTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$SnLocalChatMessageTableAnnotationComposer(
$db: db, $table: table),
updateCompanionCallback: ({
Value<int> id = const Value.absent(),
Value<int> channelId = const Value.absent(),
Value<SnChatMessage> content = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
SnLocalChatMessageCompanion(
id: id,
channelId: channelId,
content: content,
createdAt: createdAt,
),
createCompanionCallback: ({
Value<int> id = const Value.absent(),
required int channelId,
required SnChatMessage content,
Value<DateTime> createdAt = const Value.absent(),
}) =>
SnLocalChatMessageCompanion.insert(
id: id,
channelId: channelId,
content: content,
createdAt: createdAt,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
));
}
typedef $$SnLocalChatMessageTableProcessedTableManager = ProcessedTableManager<
_$AppDatabase,
$SnLocalChatMessageTable,
SnLocalChatMessageData,
$$SnLocalChatMessageTableFilterComposer,
$$SnLocalChatMessageTableOrderingComposer,
$$SnLocalChatMessageTableAnnotationComposer,
$$SnLocalChatMessageTableCreateCompanionBuilder,
$$SnLocalChatMessageTableUpdateCompanionBuilder,
(
SnLocalChatMessageData,
BaseReferences<_$AppDatabase, $SnLocalChatMessageTable,
SnLocalChatMessageData>
),
SnLocalChatMessageData,
PrefetchHooks Function()>;
class $AppDatabaseManager {
final _$AppDatabase _db;
$AppDatabaseManager(this._db);
$$SnLocalChatChannelTableTableManager get snLocalChatChannel =>
$$SnLocalChatChannelTableTableManager(_db, _db.snLocalChatChannel);
$$SnLocalChatMessageTableTableManager get snLocalChatMessage =>
$$SnLocalChatMessageTableTableManager(_db, _db.snLocalChatMessage);
}

View File

@@ -0,0 +1,8 @@
import 'package:drift/wasm.dart';
// Use `dart compile js -O4 ./drift_worker.dart` to compile this file.
// And place it in the web/ directory.
// When compiled with dart2js, this file defines a dedicated or shared web
// worker used by drift.
void main() => WasmDatabase.workerMainForOpen();

View File

@@ -13,7 +13,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -24,6 +23,7 @@ import 'package:surface/firebase_options.dart';
import 'package:surface/providers/channel.dart'; import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart'; import 'package:surface/providers/chat_call.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/database.dart';
import 'package:surface/providers/link_preview.dart'; import 'package:surface/providers/link_preview.dart';
import 'package:surface/providers/navigation.dart'; import 'package:surface/providers/navigation.dart';
import 'package:surface/providers/notification.dart'; import 'package:surface/providers/notification.dart';
@@ -40,8 +40,6 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/websocket.dart'; import 'package:surface/providers/websocket.dart';
import 'package:surface/providers/widget.dart'; import 'package:surface/providers/widget.dart';
import 'package:surface/router.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:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
@@ -50,6 +48,7 @@ import 'package:workmanager/workmanager.dart';
import 'package:in_app_review/in_app_review.dart'; import 'package:in_app_review/in_app_review.dart';
import 'package:image_picker_android/image_picker_android.dart'; import 'package:image_picker_android/image_picker_android.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:local_notifier/local_notifier.dart';
@pragma('vm:entry-point') @pragma('vm:entry-point')
void appBackgroundDispatcher() { void appBackgroundDispatcher() {
@@ -82,12 +81,6 @@ void main() async {
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(SnChannelImplAdapter());
Hive.registerAdapter(SnRealmImplAdapter());
Hive.registerAdapter(SnChannelMemberImplAdapter());
Hive.registerAdapter(SnChatMessageImplAdapter());
if (!kIsWeb && !Platform.isLinux) { if (!kIsWeb && !Platform.isLinux) {
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, options: DefaultFirebaseOptions.currentPlatform,
@@ -114,7 +107,8 @@ void main() async {
} }
if (!kIsWeb && Platform.isAndroid) { if (!kIsWeb && Platform.isAndroid) {
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance; final ImagePickerPlatform imagePickerImplementation =
ImagePickerPlatform.instance;
if (imagePickerImplementation is ImagePickerAndroid) { if (imagePickerImplementation is ImagePickerAndroid) {
imagePickerImplementation.useAndroidPhotoPicker = true; imagePickerImplementation.useAndroidPhotoPicker = true;
} }
@@ -142,6 +136,9 @@ class SolianApp extends StatelessWidget {
assetLoader: JsonAssetLoader(), assetLoader: JsonAssetLoader(),
child: MultiProvider( child: MultiProvider(
providers: [ providers: [
// Infrastructure layer
Provider(create: (ctx) => DatabaseProvider(ctx)),
// System extensions layer // System extensions layer
Provider(create: (ctx) => HomeWidgetProvider(ctx)), Provider(create: (ctx) => HomeWidgetProvider(ctx)),
@@ -230,7 +227,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
if (prefs.containsKey('first_boot_time')) { if (prefs.containsKey('first_boot_time')) {
final rawTime = prefs.getString('first_boot_time'); final rawTime = prefs.getString('first_boot_time');
final time = DateTime.tryParse(rawTime ?? ''); 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; final inAppReview = InAppReview.instance;
if (prefs.getBool('rating_requested') == true) return; if (prefs.getBool('rating_requested') == true) return;
if (await inAppReview.isAvailable()) { if (await inAppReview.isAvailable()) {
@@ -258,13 +256,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
).get( ).get(
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1', '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 remoteVersion = Version.parse(remoteVersionString.split('+').first);
final localVersion = Version.parse(localVersionString.split('+').first); final localVersion = Version.parse(localVersionString.split('+').first);
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0; final remoteBuildNumber =
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0; int.tryParse(remoteVersionString.split('+').last) ?? 0;
final localBuildNumber =
int.tryParse(localVersionString.split('+').last) ?? 0;
log("[Update] Local: $localVersionString, Remote: $remoteVersionString"); log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) { if ((remoteVersion > localVersion ||
remoteBuildNumber > localBuildNumber) &&
mounted) {
final config = context.read<ConfigProvider>(); final config = context.read<ConfigProvider>();
config.setUpdate(remoteVersionString); config.setUpdate(remoteVersionString);
log("[Update] Update available: $remoteVersionString"); log("[Update] Update available: $remoteVersionString");
@@ -301,7 +304,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
await notify.registerPushNotifications(); await notify.registerPushNotifications();
if (!mounted) return; if (!mounted) return;
final sticker = context.read<SnStickerProvider>(); final sticker = context.read<SnStickerProvider>();
await sticker.listStickerEagerly(); await sticker.listSticker();
log('[Bootstrap] Everything initialized!');
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
await context.showErrorDialog(err); await context.showErrorDialog(err);
@@ -331,7 +335,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
Future<void> _trayInitialization() async { Future<void> _trayInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; 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(); final appVersion = await PackageInfo.fromPlatform();
trayManager.addListener(this); trayManager.addListener(this);
@@ -345,6 +351,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
disabled: true, disabled: true,
), ),
MenuItem.separator(), MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem( MenuItem(
key: 'exit', key: 'exit',
label: 'trayMenuExit'.tr(), label: 'trayMenuExit'.tr(),
@@ -354,6 +364,15 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
await trayManager.setContextMenu(menu); 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; AppLifecycleListener? _appLifecycleListener;
@override @override
@@ -368,6 +387,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
_trayInitialization(); _trayInitialization();
_hotkeyInitialization(); _hotkeyInitialization();
_notifyInitialization();
_initialize().then((_) { _initialize().then((_) {
_postInitialization(); _postInitialization();
_tryRequestRating(); _tryRequestRating();
@@ -403,6 +423,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
@override @override
void onTrayMenuItemClick(MenuItem menuItem) { void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) { switch (menuItem.key) {
case 'window_show':
appWindow.show();
break;
case 'exit': case 'exit':
_appLifecycleListener?.dispose(); _appLifecycleListener?.dispose();
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); SystemChannels.platform.invokeMethod('SystemNavigator.pop');

View File

@@ -1,48 +1,54 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/controllers/chat_message_controller.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_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
class ChatChannelProvider extends ChangeNotifier { class ChatChannelProvider extends ChangeNotifier {
static const kChatChannelBoxName = 'nex_chat_channels'; static const kChatChannelBoxName = 'nex_chat_channels';
late final SnNetworkProvider _sn; late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud; late final UserDirectoryProvider _ud;
late final DatabaseProvider _dt;
Box<SnChannel>? get _channelBox => Hive.box<SnChannel>(kChatChannelBoxName); late final SnRealmProvider _rels;
ChatChannelProvider(BuildContext context) { ChatChannelProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>(); _sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>(); _ud = context.read<UserDirectoryProvider>();
_initializeLocalData(); _dt = context.read<DatabaseProvider>();
} _rels = context.read<SnRealmProvider>();
Future<void> _initializeLocalData() async {
await Hive.openBox<SnChannel>(kChatChannelBoxName);
} }
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async { Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
if (_channelBox == null) return; await Future.wait(
await _channelBox!.putAll({ channels.map(
for (final channel in channels) channel.key: channel, (ele) => _dt.db.snLocalChatChannel.insertOne(
}); SnLocalChatChannelCompanion.insert(
id: Value(ele.id),
alias: ele.key,
content: ele,
createdAt: Value(ele.createdAt),
),
onConflict: DoUpdate(
(_) => SnLocalChatChannelCompanion.custom(
content: Constant(jsonEncode(ele.toJson())),
),
),
),
),
);
} }
Future<List<SnChannel>> _fetchChannelsFromServer({ Future<List<SnChannel>> _fetchChannelsFromServer({
String scope = 'global',
bool direct = false,
bool doNotSave = false, bool doNotSave = false,
}) async { }) async {
final resp = await _sn.client.get( final resp = await _sn.client.get('/cgi/im/channels/me/available');
'/cgi/im/channels/$scope/me/available',
queryParameters: {
'direct': direct,
},
);
final out = List<SnChannel>.from( final out = List<SnChannel>.from(
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [], resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
); );
@@ -54,18 +60,21 @@ class ChatChannelProvider extends ChangeNotifier {
/// It will use the local storage as much as possible. /// It will use the local storage as much as possible.
/// The alias should include the scope, formatted as `scope:alias`. /// The alias should include the scope, formatted as `scope:alias`.
Future<SnChannel> getChannel(String key) async { Future<SnChannel> getChannel(String key) async {
if (_channelBox != null) { final local = await (_dt.db.snLocalChatChannel.select()
final local = _channelBox!.get(key); ..where((e) => e.alias.equals(key)))
if (local != null) return local; .getSingleOrNull();
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'); var resp =
await _sn.client.get('/cgi/im/channels/${key.replaceAll(':', '/')}');
var out = SnChannel.fromJson(resp.data); var out = SnChannel.fromJson(resp.data);
// Preload realm of the channel // Preload realm of the channel
if (out.realmId != null) { if (out.realmId != null) {
resp = await _sn.client.get('/cgi/id/realms/${out.realmId}'); out = out.copyWith(realm: await _rels.getRealm(out.realmId!));
out = out.copyWith(realm: SnRealm.fromJson(resp.data));
} }
_saveChannelToLocal([out]); _saveChannelToLocal([out]);
@@ -77,66 +86,63 @@ class ChatChannelProvider extends ChangeNotifier {
/// And the second time is when the data was fetched from the server. /// And the second time is when the data was fetched from the server.
/// But there is some exception that will only cause one of them to be emitted. /// But there is some exception that will only cause one of them to be emitted.
/// Like the local storage is broken or the server is down. /// Like the local storage is broken or the server is down.
Stream<List<SnChannel>> fetchChannels() async* { Stream<List<SnChannel>> fetchChannels(
if (_channelBox != null) yield _channelBox!.values.toList(); {bool noRemote = false, bool noLocal = false}) async* {
if (!noLocal) {
var resp = await _sn.client.get('/cgi/id/realms/me/available'); final local = await (_dt.db.snLocalChatChannel.select()
final realms = List<SnRealm>.from( ..orderBy([
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [], (e) =>
); OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
final realmMap = { ]))
for (final realm in realms) realm.alias: realm, .get();
}; final out = local.map((e) => e.content).toList();
for (var idx = 0; idx < out.length; idx++) {
final scopeToFetch = {'global', ...realms.map((e) => e.alias)}; final channel = out[idx];
if (channel.realmId != null) {
final List<SnChannel> result = List.empty(growable: true); out[idx] = out[idx].copyWith(
final directMessages = await _fetchChannelsFromServer( realm: await _rels.getRealm(channel.realmId!),
scope: scopeToFetch.first, );
direct: true, }
); }
result.addAll(directMessages); yield out;
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);
} }
if (noRemote) return;
final List<SnChannel> result = List.empty(growable: true);
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; yield result;
} }
Future<List<SnChatMessage>> getLastMessages( Future<List<SnChatMessage>> getLastMessages(
Iterable<SnChannel> channels, Iterable<SnChannel> channels,
) async { ) async {
final result = List<SnChatMessage>.empty(growable: true); final result = List<Future<SnLocalChatMessageData?>>.empty(growable: true);
for (final channel in channels) { for (final channel in channels) {
final channelBox = await Hive.openBox<SnChatMessage>( final out = (_dt.db.snLocalChatMessage.select()
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}', ..where((e) => e.channelId.equals(channel.id))
); ..orderBy([
final lastMessage = (e) =>
channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null; OrderingTerm(expression: e.createdAt, mode: OrderingMode.desc)
if (lastMessage != null) result.add(lastMessage); ])
channelBox.close(); ..limit(1))
.getSingleOrNull();
result.add(out);
} }
await _ud.listAccount(result.map((ele) => ele.sender.accountId).toSet()); final out = (await Future.wait(result))
return result; .where((e) => e != null)
} .map((e) => e!.content)
.toList();
@override await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
void dispose() { return out;
_channelBox?.close();
super.dispose();
} }
} }

View File

@@ -0,0 +1,31 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';
import 'package:surface/database/database.dart';
class DatabaseProvider {
late AppDatabase db;
DatabaseProvider(BuildContext context) {
db = AppDatabase();
}
Future<int> getDatabaseSize() async {
if (kIsWeb) return 0;
final basepath = await getApplicationSupportDirectory();
return await File(join(basepath.path, 'solar_network_data.sqlite'))
.length();
}
Future<void> removeDatabase() async {
if (kIsWeb) return;
final basepath = await getApplicationSupportDirectory();
final file = File(join(basepath.path, 'solar_network_data.sqlite'));
db.close();
await file.delete();
db = AppDatabase();
}
}

View File

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

View File

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

@@ -127,6 +127,7 @@ class SnPostContentProvider {
Iterable<String>? categories, Iterable<String>? categories,
Iterable<String>? tags, Iterable<String>? tags,
String? realm, String? realm,
String? channel,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
'take': take, 'take': take,
@@ -136,6 +137,7 @@ class SnPostContentProvider {
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
if (realm != null) 'realm': realm, if (realm != null) 'realm': realm,
if (channel != null) 'channel': channel,
}); });
final List<SnPost> out = await _preloadRelatedDataInBatch( final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),

View File

@@ -11,7 +11,8 @@ class SnStickerProvider {
final Map<int, List<SnSticker>> stickersByPack = {}; 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) { SnStickerProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>(); _sn = context.read<SnNetworkProvider>();
@@ -23,8 +24,18 @@ class SnStickerProvider {
void _cacheSticker(SnSticker sticker) { void _cacheSticker(SnSticker sticker) {
_cache['${sticker.pack.prefix}:${sticker.alias}'] = 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] == null) {
if (!stickersByPack[sticker.pack.id]!.contains(sticker)) stickersByPack[sticker.pack.id]!.add(sticker); 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 { Future<SnSticker?> lookupSticker(String alias) async {
@@ -46,26 +57,14 @@ class SnStickerProvider {
return null; return null;
} }
Future<void> listStickerEagerly() async { Future<void> listSticker() 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 {
try { try {
final resp = await _sn.client.get('/cgi/uc/stickers', queryParameters: { final resp = await _sn.client.get('/cgi/uc/stickers');
'take': 10,
'offset': page * 10,
});
final data = resp.data; 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) { for (final sticker in stickers) {
_cacheSticker(sticker); _cacheSticker(sticker);
} }
return data['count'] as int;
} catch (err) { } catch (err) {
log('[Sticker] Failed to list stickers: $err'); log('[Sticker] Failed to list stickers: $err');
rethrow; 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/realm/realm_discovery.dart';
import 'package:surface/screens/settings.dart'; import 'package:surface/screens/settings.dart';
import 'package:surface/screens/sharing.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/screens/wallet.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/about.dart'; import 'package:surface/widgets/about.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
Widget _fadeThroughTransition( Widget _fadeThroughTransition(BuildContext context, Animation<double> animation,
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Animation<double> secondaryAnimation, Widget child) {
return FadeThroughTransition( return FadeThroughTransition(
animation: animation, animation: animation,
secondaryAnimation: secondaryAnimation, secondaryAnimation: secondaryAnimation,
@@ -82,13 +84,15 @@ final _appRoutes = [
name: 'postSearch', name: 'postSearch',
builder: (context, state) => PostSearchScreen( builder: (context, state) => PostSearchScreen(
initialTags: state.uri.queryParameters['tags']?.split(','), initialTags: state.uri.queryParameters['tags']?.split(','),
initialCategories: state.uri.queryParameters['categories']?.split(','), initialCategories:
state.uri.queryParameters['categories']?.split(','),
), ),
), ),
GoRoute( GoRoute(
path: '/publishers/:name', path: '/publishers/:name',
name: 'postPublisher', name: 'postPublisher',
builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!), builder: (context, state) =>
PostPublisherScreen(name: state.pathParameters['name']!),
), ),
GoRoute( GoRoute(
path: '/:slug', path: '/:slug',
@@ -100,52 +104,56 @@ final _appRoutes = [
), ),
], ],
), ),
GoRoute(path: '/account', name: 'account', builder: (context, state) => const AccountScreen(), routes: [ GoRoute(
GoRoute( path: '/account',
path: '/wallet', name: 'account',
name: 'accountWallet', builder: (context, state) => const AccountScreen(),
builder: (context, state) => const WalletScreen(), routes: [
), GoRoute(
GoRoute( path: '/wallet',
path: '/settings', name: 'accountWallet',
name: 'accountSettings', builder: (context, state) => const WalletScreen(),
builder: (context, state) => AccountSettingsScreen(), ),
), GoRoute(
GoRoute( path: '/settings',
path: '/settings/factors', name: 'accountSettings',
name: 'factorSettings', builder: (context, state) => AccountSettingsScreen(),
builder: (context, state) => FactorSettingsScreen(), ),
), GoRoute(
GoRoute( path: '/settings/factors',
path: '/profile/edit', name: 'factorSettings',
name: 'accountProfileEdit', builder: (context, state) => FactorSettingsScreen(),
builder: (context, state) => ProfileEditScreen(), ),
), GoRoute(
GoRoute( path: '/profile/edit',
path: '/publishers', name: 'accountProfileEdit',
name: 'accountPublishers', builder: (context, state) => ProfileEditScreen(),
builder: (context, state) => PublisherScreen(), ),
), GoRoute(
GoRoute( path: '/publishers',
path: '/publishers/new', name: 'accountPublishers',
name: 'accountPublisherNew', builder: (context, state) => PublisherScreen(),
builder: (context, state) => AccountPublisherNewScreen(), ),
), GoRoute(
GoRoute( path: '/publishers/new',
path: '/publishers/edit/:name', name: 'accountPublisherNew',
name: 'accountPublisherEdit', builder: (context, state) => AccountPublisherNewScreen(),
builder: (context, state) => AccountPublisherEditScreen( ),
name: state.pathParameters['name']!, GoRoute(
), path: '/publishers/edit/:name',
), name: 'accountPublisherEdit',
GoRoute( builder: (context, state) => AccountPublisherEditScreen(
path: '/:name', name: state.pathParameters['name']!,
name: 'accountProfilePage', ),
pageBuilder: (context, state) => NoTransitionPage( ),
child: UserScreen(name: state.pathParameters['name']!), GoRoute(
), path: '/:name',
), name: 'accountProfilePage',
]), pageBuilder: (context, state) => NoTransitionPage(
child: UserScreen(name: state.pathParameters['name']!),
),
),
]),
GoRoute( GoRoute(
path: '/chat', path: '/chat',
name: 'chat', name: 'chat',
@@ -208,19 +216,39 @@ final _appRoutes = [
GoRoute( GoRoute(
path: '/:alias', path: '/:alias',
name: 'realmDetail', 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(
GoRoute( path: '/news',
path: '/:hash', name: 'news',
name: 'newsDetail', builder: (context, state) => const NewsScreen(),
builder: (context, state) => NewsDetailScreen( routes: [
hash: state.pathParameters['hash']!, 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( GoRoute(
path: '/album', path: '/album',
name: 'album', name: 'album',

View File

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

View File

@@ -5,21 +5,23 @@ import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:surface/providers/channel.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/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/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/account/account_select.dart';
import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.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/navigation/app_scaffold.dart';
import 'package:surface/widgets/unauthorized_hint.dart'; import 'package:surface/widgets/unauthorized_hint.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../providers/sn_network.dart';
import '../providers/userinfo.dart';
class ChatScreen extends StatefulWidget { class ChatScreen extends StatefulWidget {
const ChatScreen({super.key}); const ChatScreen({super.key});
@@ -34,8 +36,18 @@ class _ChatScreenState extends State<ChatScreen> {
List<SnChannel>? _channels; List<SnChannel>? _channels;
Map<int, SnChatMessage>? _lastMessages; Map<int, SnChatMessage>? _lastMessages;
Map<int, int>? _unreadCounts;
void _refreshChannels() { 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>(); final ua = context.read<UserProvider>();
if (!ua.isAuthorized) { if (!ua.isAuthorized) {
setState(() => _isBusy = false); setState(() => _isBusy = false);
@@ -43,12 +55,15 @@ class _ChatScreenState extends State<ChatScreen> {
} }
final chan = context.read<ChatChannelProvider>(); final chan = context.read<ChatChannelProvider>();
chan.fetchChannels().listen((channels) async { chan.fetchChannels(noRemote: noRemote).listen((channels) async {
final lastMessages = await chan.getLastMessages(channels); final lastMessages = await chan.getLastMessages(channels);
_lastMessages = {for (final val in lastMessages) val.channelId: val}; _lastMessages = {for (final val in lastMessages) val.channelId: val};
channels.sort((a, b) { channels.sort((a, b) {
if (_lastMessages!.containsKey(a.id) && _lastMessages!.containsKey(b.id)) { if (_lastMessages!.containsKey(a.id) &&
return _lastMessages![b.id]!.createdAt.compareTo(_lastMessages![a.id]!.createdAt); _lastMessages!.containsKey(b.id)) {
return _lastMessages![b.id]!
.createdAt
.compareTo(_lastMessages![a.id]!.createdAt);
} }
if (_lastMessages!.containsKey(a.id)) return -1; if (_lastMessages!.containsKey(a.id)) return -1;
if (_lastMessages!.containsKey(b.id)) return 1; if (_lastMessages!.containsKey(b.id)) return 1;
@@ -86,7 +101,8 @@ class _ChatScreenState extends State<ChatScreen> {
void _newDirectMessage() async { void _newDirectMessage() async {
final user = await showModalBottomSheet( final user = await showModalBottomSheet(
context: context, context: context,
builder: (context) => AccountSelect(title: 'channelNewDirectMessage'.tr()), builder: (context) =>
AccountSelect(title: 'channelNewDirectMessage'.tr()),
); );
if (user == null) return; if (user == null) return;
if (!mounted) return; if (!mounted) return;
@@ -98,7 +114,8 @@ class _ChatScreenState extends State<ChatScreen> {
await sn.client.post('/cgi/im/channels/global/dm', data: { await sn.client.post('/cgi/im/channels/global/dm', data: {
'alias': uuid.v4().replaceAll('-', '').substring(0, 12), 'alias': uuid.v4().replaceAll('-', '').substring(0, 12),
'name': 'DM', 'name': 'DM',
'description': 'A direct message channel between @${ua.user?.name} and @${user.name}', 'description':
'A direct message channel between @${ua.user?.name} and @${user.name}',
'related_user': user.id, 'related_user': user.id,
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
@@ -109,10 +126,13 @@ class _ChatScreenState extends State<ChatScreen> {
} }
} }
SnChannel? _focusChannel;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_refreshChannels(); _refreshChannels();
_fetchWhatsNew();
} }
@override @override
@@ -132,7 +152,10 @@ class _ChatScreenState extends State<ChatScreen> {
); );
} }
return AppScaffold( final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
final chatList = AppScaffold(
noBackground: doExpand,
appBar: AppBar( appBar: AppBar(
leading: AutoAppBarLeading(), leading: AutoAppBarLeading(),
title: Text('screenChat').tr(), title: Text('screenChat').tr(),
@@ -144,20 +167,27 @@ class _ChatScreenState extends State<ChatScreen> {
type: ExpandableFabType.up, type: ExpandableFabType.up,
childrenAnimation: ExpandableFabAnimation.none, childrenAnimation: ExpandableFabAnimation.none,
overlayStyle: ExpandableFabOverlayStyle( 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( openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(Symbols.add, size: 28), child: const Icon(Symbols.add, size: 28),
fabSize: ExpandableFabSize.regular, fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, foregroundColor:
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(), shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
fabSize: ExpandableFabSize.regular, fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, foregroundColor:
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(), shape: const CircleBorder(),
), ),
children: [ children: [
@@ -200,7 +230,10 @@ class _ChatScreenState extends State<ChatScreen> {
context: context, context: context,
removeTop: true, removeTop: true,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => Future.sync(() => _refreshChannels()), onRefresh: () => Future.wait([
Future.sync(() => _refreshChannels()),
_fetchWhatsNew(),
]),
child: ListView.builder( child: ListView.builder(
itemCount: _channels?.length ?? 0, itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
@@ -208,13 +241,29 @@ class _ChatScreenState extends State<ChatScreen> {
final lastMessage = _lastMessages?[channel.id]; final lastMessage = _lastMessages?[channel.id];
if (channel.type == 1) { if (channel.type == 1) {
final otherMember = channel.members?.cast<SnChannelMember?>().firstWhere( final otherMember =
(ele) => ele?.accountId != ua.user?.id, channel.members?.cast<SnChannelMember?>().firstWhere(
orElse: () => null, (ele) => ele?.accountId != ua.user?.id,
); orElse: () => null,
);
return ListTile( 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 subtitle: lastMessage != null
? Text( ? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@@ -222,17 +271,22 @@ class _ChatScreenState extends State<ChatScreen> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
) )
: Text( : Text(
'channelDirectMessageDescription'.tr(args: [ channel.description,
'@${ud.getAccountFromCache(otherMember?.accountId)?.name}',
]),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage( leading: AccountImage(
content: ud.getAccountFromCache(otherMember?.accountId)?.avatar, content: ud
.getAccountFromCache(otherMember?.accountId)
?.avatar,
), ),
onTap: () { onTap: () {
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'chatRoom', 'chatRoom',
pathParameters: { pathParameters: {
@@ -240,14 +294,23 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (mounted) _refreshChannels(); if (mounted) _refreshChannels(noRemote: true);
}); });
}, },
); );
} }
return ListTile( 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 subtitle: lastMessage != null
? Text( ? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}', '${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
@@ -259,12 +322,17 @@ class _ChatScreenState extends State<ChatScreen> {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage( leading: AccountImage(
content: null, content: null,
fallbackWidget: const Icon(Symbols.chat, size: 20), fallbackWidget: const Icon(Symbols.chat, size: 20),
), ),
onTap: () { onTap: () {
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'chatRoom', 'chatRoom',
pathParameters: { pathParameters: {
@@ -272,7 +340,7 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (value == true) _refreshChannels(); if (value == true) _refreshChannels(noRemote: true);
}); });
}, },
); );
@@ -284,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 String alias;
final ChatRoomScreenExtra? extra; 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 @override
State<ChatRoomScreen> createState() => _ChatRoomScreenState(); State<ChatRoomScreen> createState() => _ChatRoomScreenState();
@@ -192,10 +193,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
log('[ChatInput] Setting initial text and attachments...'); log('[ChatInput] Setting initial text and attachments...');
if (widget.extra!.initialText != null) { if (widget.extra!.initialText != null) {
_inputGlobalKey.currentState?.setInitialText(widget.extra!.initialText!); _inputGlobalKey.currentState
?.setInitialText(widget.extra!.initialText!);
} }
if (widget.extra!.initialAttachments != null) { 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( appBar: AppBar(
title: Text( title: Text(
_channel?.type == 1 _channel?.type == 1
? ud.getAccountFromCache(_otherMember?.accountId)?.nick ?? _channel!.name ? ud.getAccountFromCache(_otherMember?.accountId)?.nick ??
_channel!.name
: _channel?.name ?? 'loading'.tr(), : _channel?.name ?? 'loading'.tr(),
), ),
actions: [ actions: [
IconButton( 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 onPressed: _isCalling
? null ? null
: _ongoingCall == null : _ongoingCall == null
@@ -296,9 +302,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
) )
], ],
), ),
) ).height(_ongoingCall != null ? 54 : 0, animate: true).animate(
.height(_ongoingCall != null ? 54 : 0, animate: true) const Duration(milliseconds: 300),
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn), Curves.fastLinearToSlowEaseIn),
if (_messageController.isPending) if (_messageController.isPending)
Expanded( Expanded(
child: const CircularProgressIndicator().center(), child: const CircularProgressIndicator().center(),
@@ -316,6 +322,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
}, },
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final message = _messageController.messages[idx]; final message = _messageController.messages[idx];
_messageController.readEvent(message.id);
bool canMerge = false, canMergePrevious = false; bool canMerge = false, canMergePrevious = false;
if (idx > 0) { if (idx > 0) {
@@ -337,7 +344,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
data: message, data: message,
isMerged: canMerge, isMerged: canMerge,
hasMerged: canMergePrevious, hasMerged: canMergePrevious,
isPending: _messageController.unconfirmedMessages.contains(message.uuid), isPending: _messageController.unconfirmedMessages
.contains(message.uuid),
onReply: (value) { onReply: (value) {
_inputGlobalKey.currentState?.setReply(value); _inputGlobalKey.currentState?.setReply(value);
}, },

View File

@@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
@@ -8,7 +9,10 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart'; import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
@@ -35,61 +39,54 @@ class ExploreScreen extends StatefulWidget {
State<ExploreScreen> createState() => _ExploreScreenState(); State<ExploreScreen> createState() => _ExploreScreenState();
} }
class _ExploreScreenState extends State<ExploreScreen> { // You know what? I'm not going to make this a global variable.
// 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);
final _fabKey = GlobalKey<ExpandableFabState>(); final _fabKey = GlobalKey<ExpandableFabState>();
final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>());
bool _isBusy = true;
final List<SnPost> _posts = List.empty(growable: true);
final List<SnPostCategory> _categories = List.empty(growable: true); final List<SnPostCategory> _categories = List.empty(growable: true);
int? _postCount;
String? _selectedCategory;
Future<void> _fetchCategories() async { Future<void> _fetchCategories() async {
_categories.clear(); _categories.clear();
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/categories?take=100'); final resp = await sn.client.get('/cgi/co/categories?take=100');
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []); setState(() {
_categories.addAll(resp.data
.map((e) => SnPostCategory.fromJson(e))
.cast<SnPostCategory>() ??
[]);
});
} catch (err) { } catch (err) {
if (!mounted) return; if (mounted) context.showErrorDialog(err);
context.showErrorDialog(err);
} }
} }
Future<void> _fetchPosts() async { void _clearFilter() {
if (_postCount != null && _posts.length >= _postCount!) return; _selectedCategory = null;
setState(() => _isBusy = true);
final pt = context.read<SnPostContentProvider>();
final result = await pt.listPosts(
take: 10,
offset: _posts.length,
categories: _selectedCategory != null ? [_selectedCategory!] : null,
);
final out = result.$1;
if (!mounted) return;
_postCount = result.$2;
_posts.addAll(out);
if (mounted) setState(() => _isBusy = false);
}
Future<void> _refreshPosts() {
_postCount = null;
_posts.clear();
return _fetchPosts();
} }
@override @override
void initState() { void initState() {
super.initState();
_fetchPosts();
_fetchCategories(); _fetchCategories();
super.initState();
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
Future<void> refreshPosts() async {
await _listKeys[_tabController.index].currentState?.refreshPosts();
} }
@override @override
@@ -102,20 +99,27 @@ class _ExploreScreenState extends State<ExploreScreen> {
type: ExpandableFabType.up, type: ExpandableFabType.up,
childrenAnimation: ExpandableFabAnimation.none, childrenAnimation: ExpandableFabAnimation.none,
overlayStyle: ExpandableFabOverlayStyle( 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( openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(Symbols.add, size: 28), child: const Icon(Symbols.add, size: 28),
fabSize: ExpandableFabSize.regular, fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, foregroundColor:
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(), shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
fabSize: ExpandableFabSize.regular, fabSize: ExpandableFabSize.regular,
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor, foregroundColor:
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(), shape: const CircleBorder(),
), ),
children: [ children: [
@@ -131,7 +135,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
'mode': 'stories', 'mode': 'stories',
}).then((value) { }).then((value) {
if (value == true) { if (value == true) {
_refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
@@ -152,7 +156,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
'mode': 'articles', 'mode': 'articles',
}).then((value) { }).then((value) {
if (value == true) { if (value == true) {
_refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
@@ -173,7 +177,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
'mode': 'questions', 'mode': 'questions',
}).then((value) { }).then((value) {
if (value == true) { if (value == true) {
_refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
@@ -194,7 +198,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
'mode': 'videos', 'mode': 'videos',
}).then((value) { }).then((value) {
if (value == true) { if (value == true) {
_refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
@@ -205,74 +209,157 @@ class _ExploreScreenState extends State<ExploreScreen> {
), ),
], ],
), ),
body: RefreshIndicator( body: NestedScrollView(
displacement: 40 + MediaQuery.of(context).padding.top, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
onRefresh: () => _refreshPosts(), return [
child: CustomScrollView( SliverOverlapAbsorber(
slivers: [ handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
SliverAppBar( sliver: SliverAppBar(
leading: AutoAppBarLeading(), leading: AutoAppBarLeading(),
title: Text('screenExplore').tr(), title: Text('screenExplore').tr(),
floating: true, floating: true,
snap: true, snap: true,
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.search), icon: const Icon(Symbols.category),
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed('postSearch'); showModalBottomSheet(
}, context: context,
), builder: (context) => _PostCategoryPickerPopup(
const Gap(8), categories: _categories,
], selected: _selectedCategory,
bottom: PreferredSize( ),
preferredSize: const Size.fromHeight(50), ).then((value) {
child: SizedBox( if (value != null && context.mounted) {
height: 50, _selectedCategory = value == false ? null : value;
child: SingleChildScrollView( refreshPosts();
scrollDirection: Axis.horizontal, }
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12), });
child: Row( },
mainAxisAlignment: MainAxisAlignment.center,
children: _categories.map((ele) {
return StyledWidget(ChoiceChip(
avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark),
label: Text(
'postCategory${ele.alias.capitalize()}'.trExists()
? 'postCategory${ele.alias.capitalize()}'.tr()
: ele.name,
),
selected: _selectedCategory == ele.alias,
onSelected: (value) {
_selectedCategory = value ? ele.alias : null;
_refreshPosts();
},
)).padding(horizontal: 4);
}).toList(),
),
), ),
IconButton(
icon: const Icon(Symbols.search),
onPressed: () {
GoRouter.of(context).pushNamed('postSearch');
},
),
const Gap(8),
],
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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),
),
],
),
),
],
), ),
), ),
), ),
const SliverGap(12), ];
SliverInfiniteList( },
itemCount: _posts.length, body: TabBarView(
isLoading: _isBusy, controller: _tabController,
centerLoading: true, children: [
hasReachedMax: _postCount != null && _posts.length >= _postCount!, _PostListWidget(
onFetchData: _fetchPosts, key: _listKeys[0],
itemBuilder: (context, idx) { onClearFilter: _clearFilter,
return OpenablePostItem( ),
data: _posts[idx], _PostListWidget(
maxWidth: 640, key: _listKeys[1],
onChanged: (data) { channel: 'friends',
setState(() => _posts[idx] = data); onClearFilter: _clearFilter,
}, ),
onDeleted: () { _PostListWidget(
_refreshPosts(); key: _listKeys[2],
}, channel: 'following',
); onClearFilter: _clearFilter,
}, ),
separatorBuilder: (_, __) => const Gap(8), _PostListWidget(
key: _listKeys[3],
withRealm: true,
onClearFilter: _clearFilter,
), ),
], ],
), ),
@@ -280,3 +367,261 @@ class _ExploreScreenState extends State<ExploreScreen> {
); );
} }
} }
class _PostListWidget extends StatefulWidget {
final String? channel;
final bool withRealm;
final Function onClearFilter;
const _PostListWidget(
{super.key,
this.channel,
this.withRealm = false,
required this.onClearFilter});
@override
State<_PostListWidget> createState() => _PostListWidgetState();
}
class _PostListWidgetState extends State<_PostListWidget> {
bool _isBusy = false;
final List<SnPost> _posts = List.empty(growable: true);
final List<SnRealm> _realms = List.empty(growable: true);
SnRealm? _selectedRealm;
int? _postCount;
Future<void> _fetchRealms() async {
try {
final rels = context.read<SnRealmProvider>();
final out = await rels.listAvailableRealms();
setState(() {
_realms.addAll(out);
_selectedRealm = out.firstOrNull;
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
rethrow;
}
}
Future<void> _fetchPosts() async {
if (_postCount != null && _posts.length >= _postCount!) return;
setState(() => _isBusy = true);
final pt = context.read<SnPostContentProvider>();
final result = await pt.listPosts(
take: 10,
offset: _posts.length,
categories: _selectedCategory != null ? [_selectedCategory!.alias] : null,
channel: widget.channel,
realm: _selectedRealm?.alias,
);
final out = result.$1;
if (!mounted) return;
_postCount = result.$2;
_posts.addAll(out);
if (mounted) setState(() => _isBusy = false);
}
Future<void> refreshPosts() {
_postCount = null;
_posts.clear();
return _fetchPosts();
}
@override
void initState() {
super.initState();
if (widget.withRealm) {
_fetchRealms().then((_) {
_fetchPosts();
});
} else {
_fetchPosts();
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (_selectedCategory != null)
MaterialBanner(
content: Text(
'postFilterWithCategory'.tr(args: [
'postCategory${_selectedCategory!.alias.capitalize()}'.trExists()
? 'postCategory${_selectedCategory!.alias.capitalize()}'
.tr()
: _selectedCategory!.name,
]),
),
leading: Icon(kCategoryIcons[_selectedCategory!.alias] ??
Symbols.question_mark),
actions: [
IconButton(
icon: const Icon(Symbols.clear),
onPressed: () {
widget.onClearFilter.call();
refreshPosts();
},
),
],
padding: const EdgeInsets.only(left: 20, right: 4),
),
if (widget.withRealm)
DropdownButtonHideUnderline(
child: DropdownButton2<SnRealm>(
isExpanded: true,
items: _realms
.map(
(ele) => DropdownMenuItem<SnRealm>(
value: ele,
child: Row(
children: [
AccountImage(
content: ele.avatar,
fallbackWidget: const Icon(Symbols.group, size: 16),
radius: 14,
),
const Gap(8),
Text(
ele.name,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
)
.toList(),
value: _selectedRealm,
onChanged: (SnRealm? value) {
setState(() => _selectedRealm = value);
refreshPosts();
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(left: 4, right: 12),
),
menuItemStyleData: const MenuItemStyleData(
height: 48,
),
),
),
if (widget.withRealm) const Divider(height: 1),
Expanded(
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
displacement: 40 + MediaQuery.of(context).padding.top,
onRefresh: () => refreshPosts(),
child: InfiniteList(
itemCount: _posts.length,
isLoading: _isBusy,
centerLoading: true,
hasReachedMax:
_postCount != null && _posts.length >= _postCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
return OpenablePostItem(
data: _posts[idx],
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
refreshPosts();
},
);
},
separatorBuilder: (_, __) => const Gap(8),
),
),
).padding(top: 8),
),
],
);
}
}
class _PostCategoryPickerPopup extends StatelessWidget {
final List<SnPostCategory> categories;
final SnPostCategory? selected;
const _PostCategoryPickerPopup({required this.categories, this.selected});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.category, size: 24),
const Gap(16),
Text('postCategory')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
ListTile(
leading: const Icon(Symbols.clear),
title: Text('postFilterReset').tr(),
subtitle: Text('postFilterResetDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
onTap: () {
Navigator.pop(context, false);
},
),
const Divider(height: 1),
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(),
),
),
],
);
}
}

View File

@@ -5,8 +5,10 @@ import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -14,7 +16,10 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.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_network.dart';
import 'package:surface/providers/sn_sticker.dart';
import 'package:surface/providers/theme.dart'; import 'package:surface/providers/theme.dart';
import 'package:surface/theme.dart'; import 'package:surface/theme.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
@@ -67,6 +72,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final dt = context.read<DatabaseProvider>();
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
@@ -81,7 +87,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('settingsAppearance')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile( ListTile(
title: Text('settingsDisplayLanguage').tr(), title: Text('settingsDisplayLanguage').tr(),
subtitle: Text('settingsDisplayLanguageDescription').tr(), subtitle: Text('settingsDisplayLanguageDescription').tr(),
@@ -91,15 +101,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: DropdownButton2<Locale?>( child: DropdownButton2<Locale?>(
isExpanded: true, isExpanded: true,
items: [ items: [
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) { ...EasyLocalization.of(context)!
.supportedLocales
.mapIndexed((idx, ele) {
return DropdownMenuItem<Locale?>( return DropdownMenuItem<Locale?>(
value: ele, value: ele,
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14), child:
Text('${ele.languageCode}-${ele.countryCode}')
.fontSize(14),
); );
}), }),
DropdownMenuItem<Locale?>( DropdownMenuItem<Locale?>(
value: null, value: null,
child: Text('settingsDisplayLanguageSystem').tr().fontSize(14), child: Text('settingsDisplayLanguageSystem')
.tr()
.fontSize(14),
), ),
], ],
value: EasyLocalization.of(context)!.currentLocale, value: EasyLocalization.of(context)!.currentLocale,
@@ -132,10 +148,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
leading: const Icon(Symbols.image), leading: const Icon(Symbols.image),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
onTap: () async { onTap: () async {
final image = await ImagePicker().pickImage(source: ImageSource.gallery); final image = await ImagePicker()
.pickImage(source: ImageSource.gallery);
if (image == null) return; if (image == null) return;
await File(image.path).copy('$_docBasepath/app_background_image'); await File(image.path)
.copy('$_docBasepath/app_background_image');
_prefs.setBool(kAppBackgroundStoreKey, true); _prefs.setBool(kAppBackgroundStoreKey, true);
setState(() {}); setState(() {});
@@ -143,7 +161,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
if (!kIsWeb) if (!kIsWeb)
FutureBuilder<bool>( FutureBuilder<bool>(
future: File('$_docBasepath/app_background_image').exists(), future:
File('$_docBasepath/app_background_image').exists(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data!) { if (!snapshot.hasData || !snapshot.data!) {
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -151,12 +170,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
return ListTile( return ListTile(
title: Text('settingsBackgroundImageClear').tr(), title: Text('settingsBackgroundImageClear').tr(),
subtitle: Text('settingsBackgroundImageClearDescription').tr(), subtitle:
contentPadding: const EdgeInsets.symmetric(horizontal: 24), Text('settingsBackgroundImageClearDescription')
.tr(),
contentPadding:
const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.texture), leading: const Icon(Symbols.texture),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
onTap: () { onTap: () {
File('$_docBasepath/app_background_image').deleteSync(); File('$_docBasepath/app_background_image')
.deleteSync();
_prefs.remove(kAppBackgroundStoreKey); _prefs.remove(kAppBackgroundStoreKey);
setState(() {}); setState(() {});
}, },
@@ -186,34 +209,35 @@ class _SettingsScreenState extends State<SettingsScreen> {
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
onTap: () async { onTap: () async {
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value); Color pickerColor = Color(
_prefs.getInt(kAppColorSchemeStoreKey) ??
Colors.indigo.value);
final color = await showDialog<Color?>( final color = await showDialog<Color?>(
context: context, context: context,
builder: (context) => builder: (context) => AlertDialog(
AlertDialog( content: SingleChildScrollView(
content: SingleChildScrollView( child: ColorPicker(
child: ColorPicker( pickerColor: pickerColor,
pickerColor: pickerColor, onColorChanged: (color) => pickerColor = color,
onColorChanged: (color) => pickerColor = color, enableAlpha: false,
enableAlpha: false, hexInputBar: true,
hexInputBar: true,
),
),
actions: <Widget>[
TextButton(
child: const Text('dialogDismiss').tr(),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('dialogConfirm').tr(),
onPressed: () {
Navigator.of(context).pop(pickerColor);
},
),
],
), ),
),
actions: <Widget>[
TextButton(
child: const Text('dialogDismiss').tr(),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('dialogConfirm').tr(),
onPressed: () {
Navigator.of(context).pop(pickerColor);
},
),
],
),
); );
if (color == null || !context.mounted) return; if (color == null || !context.mounted) return;
@@ -248,16 +272,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
], ],
value: _prefs.getInt(kAppColorSchemeStoreKey) == null value: _prefs.getInt(kAppColorSchemeStoreKey) == null
? 1 ? 1
: kColorSchemes.values : kColorSchemes.values.toList().indexWhere((ele) =>
.toList() ele.value ==
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)), _prefs.getInt(kAppColorSchemeStoreKey)),
onChanged: (int? value) { onChanged: (int? value) {
if (value != null && value != -1) { if (value != null && value != -1) {
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values _prefs.setInt(kAppColorSchemeStoreKey,
.elementAt(value) kColorSchemes.values.elementAt(value).value);
.value);
final th = context.read<ThemeProvider>(); final th = context.read<ThemeProvider>();
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value)); th.reloadTheme(
seedColorOverride:
kColorSchemes.values.elementAt(value));
setState(() {}); setState(() {});
context.showSnackbar('colorSchemeApplied'.tr()); context.showSnackbar('colorSchemeApplied'.tr());
@@ -293,7 +318,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
CheckboxListTile( CheckboxListTile(
secondary: const Icon(Symbols.left_panel_close), secondary: const Icon(Symbols.left_panel_close),
title: Text('settingsDrawerPreferCollapse').tr(), title: Text('settingsDrawerPreferCollapse').tr(),
subtitle: Text('settingsDrawerPreferCollapseDescription').tr(), subtitle:
Text('settingsDrawerPreferCollapseDescription').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17), contentPadding: const EdgeInsets.only(left: 24, right: 17),
value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false, value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false,
onChanged: (value) { onChanged: (value) {
@@ -308,7 +334,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('settingsFeatures').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('settingsFeatures')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
CheckboxListTile( CheckboxListTile(
secondary: const Icon(Symbols.vibration), secondary: const Icon(Symbols.vibration),
contentPadding: const EdgeInsets.only(left: 24, right: 17), contentPadding: const EdgeInsets.only(left: 24, right: 17),
@@ -350,7 +380,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('settingsNetwork').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('settingsNetwork')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
TextField( TextField(
controller: _serverUrlController, controller: _serverUrlController,
decoration: InputDecoration( decoration: InputDecoration(
@@ -371,7 +405,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
}, },
), ),
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 16, top: 8, bottom: 4), ).padding(horizontal: 16, top: 8, bottom: 4),
ListTile( ListTile(
title: Text('settingsNetworkServerPreset').tr(), title: Text('settingsNetworkServerPreset').tr(),
@@ -383,12 +418,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
isExpanded: true, isExpanded: true,
items: [ items: [
...kNetworkServerDirectory, ...kNetworkServerDirectory,
if (!kNetworkServerDirectory.map((ele) => ele.$2).contains(_serverUrlController.text)) if (!kNetworkServerDirectory
.map((ele) => ele.$2)
.contains(_serverUrlController.text))
('Custom', _serverUrlController.text), ('Custom', _serverUrlController.text),
] ]
.map( .map(
(item) => (item) => DropdownMenuItem<String>(
DropdownMenuItem<String>(
value: item.$2, value: item.$2,
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
@@ -396,11 +432,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(item.$1).fontSize(14), Text(item.$1).fontSize(14),
Text(item.$2, overflow: TextOverflow.ellipsis).fontSize(11) Text(item.$2, overflow: TextOverflow.ellipsis)
.fontSize(11)
], ],
), ),
), ),
) )
.toList(), .toList(),
value: _serverUrlController.text, value: _serverUrlController.text,
onChanged: (String? value) { onChanged: (String? value) {
@@ -442,7 +479,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('settingsPerformance')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile( ListTile(
title: Text('settingsImageQuality').tr(), title: Text('settingsImageQuality').tr(),
subtitle: Text('settingsImageQualityDescription').tr(), subtitle: Text('settingsImageQualityDescription').tr(),
@@ -450,21 +491,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
leading: const Icon(Symbols.image), leading: const Icon(Symbols.image),
trailing: DropdownButtonHideUnderline( trailing: DropdownButtonHideUnderline(
child: DropdownButton2<FilterQuality>( child: DropdownButton2<FilterQuality>(
value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ?? value: kImageQualityLevel.values.elementAtOrNull(
_prefs.getInt('app_image_quality') ?? 3) ??
FilterQuality.high, FilterQuality.high,
isExpanded: true, isExpanded: true,
items: kImageQualityLevel.entries items: kImageQualityLevel.entries
.map( .map(
(item) => (item) => DropdownMenuItem<FilterQuality>(
DropdownMenuItem<FilterQuality>(
value: item.value, value: item.value,
child: Text(item.key).tr().fontSize(14), child: Text(item.key).tr().fontSize(14),
), ),
) )
.toList(), .toList(),
onChanged: (FilterQuality? value) { onChanged: (FilterQuality? value) {
if (value == null) return; if (value == null) return;
_prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value)); _prefs.setInt('app_image_quality',
kImageQualityLevel.values.toList().indexOf(value));
setState(() {}); setState(() {});
}, },
buttonStyleData: const ButtonStyleData( buttonStyleData: const ButtonStyleData(
@@ -486,7 +528,82 @@ class _SettingsScreenState extends State<SettingsScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('settingsMisc').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('settingsMisc')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile(
leading: const Icon(Symbols.database),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text('databaseSize').tr(),
subtitle: FutureBuilder(
future: dt.getDatabaseSize(),
builder: (context, snapshot) {
if (!snapshot.hasData || kIsWeb) {
return Text('unknown').tr();
}
return Text(
snapshot.data!.formatBytes(),
style: GoogleFonts.robotoMono(),
);
},
),
),
ListTile(
leading: const Icon(Symbols.database_off),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text('databaseDelete').tr(),
subtitle: Text('databaseDeleteDescription').tr(),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
await dt.removeDatabase();
if (!context.mounted) return;
HapticFeedback.heavyImpact();
context.showSnackbar('databaseDeleted'.tr());
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( ListTile(
title: Text('settingsMiscAbout').tr(), title: Text('settingsMiscAbout').tr(),
subtitle: Text('settingsMiscAboutDescription').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:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
part 'account.freezed.dart'; part 'account.freezed.dart';
part 'account.g.dart'; part 'account.g.dart';
@@ -9,7 +8,7 @@ class SnAccount with _$SnAccount {
const SnAccount._(); const SnAccount._();
const factory SnAccount({ const factory SnAccount({
@HiveField(0) required int id, required int id,
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,

View File

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

View File

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

View File

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

View File

@@ -2,214 +2,6 @@
part of 'chat.dart'; 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 // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************

View File

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

View File

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

View File

@@ -2,80 +2,6 @@
part of 'realm.dart'; 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 // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ class AppScaffold extends StatelessWidget {
final AppBar? appBar; final AppBar? appBar;
final DrawerCallback? onDrawerChanged; final DrawerCallback? onDrawerChanged;
final DrawerCallback? onEndDrawerChanged; final DrawerCallback? onEndDrawerChanged;
final bool noBackground;
const AppScaffold({ const AppScaffold({
super.key, super.key,
@@ -45,6 +46,7 @@ class AppScaffold extends StatelessWidget {
this.endDrawer, this.endDrawer,
this.onDrawerChanged, this.onDrawerChanged,
this.onEndDrawerChanged, this.onEndDrawerChanged,
this.noBackground = false,
}); });
@override @override
@@ -52,20 +54,23 @@ class AppScaffold extends StatelessWidget {
final appBarHeight = appBar?.preferredSize.height ?? 0; final appBarHeight = appBar?.preferredSize.height ?? 0;
final safeTop = MediaQuery.of(context).padding.top; 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( return Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SizedBox.expand( body: SizedBox.expand(
child: AppBackground( child: noBackground
isRoot: true, ? content
child: Column( : AppBackground(isRoot: true, child: content),
children: [
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
if (body != null) Expanded(child: body!),
],
),
),
), ),
appBar: appBar, appBar: appBar,
bottomNavigationBar: bottomNavigationBar, bottomNavigationBar: bottomNavigationBar,
@@ -107,11 +112,19 @@ class AppRootScaffold extends StatelessWidget {
final isCollapseDrawer = cfg.drawerIsCollapsed; final isCollapseDrawer = cfg.drawerIsCollapsed;
final isExpandedDrawer = cfg.drawerIsExpanded; final isExpandedDrawer = cfg.drawerIsExpanded;
final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name; final routeName = GoRouter.of(context)
final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName) .routerDelegate
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) .currentConfiguration
: false; .last
final isPopable = !NavigationProvider.kAllDestination.map((ele) => ele.screen).contains(routeName); .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 final innerWidget = isCollapseDrawer
? body ? body
@@ -126,7 +139,9 @@ class AppRootScaffold extends StatelessWidget {
), ),
), ),
), ),
child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(), child: isExpandedDrawer
? AppNavigationDrawer(elevation: 0)
: AppRailNavigation(),
), ),
Expanded(child: body), Expanded(child: body),
], ],
@@ -150,7 +165,8 @@ class AppRootScaffold extends StatelessWidget {
children: [ children: [
Column( Column(
children: [ children: [
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) if (!kIsWeb &&
(Platform.isWindows || Platform.isLinux || Platform.isMacOS))
WindowTitleBarBox( WindowTitleBarBox(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -164,12 +180,19 @@ class AppRootScaffold extends StatelessWidget {
child: MoveWindow( child: MoveWindow(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start, mainAxisAlignment: Platform.isMacOS
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
'Solar Network', 'Solar Network',
style: GoogleFonts.spaceGrotesk(), style: GoogleFonts.spaceGrotesk(),
textAlign: !kIsWeb
? Platform.isMacOS
? TextAlign.center
: null
: null,
).padding(horizontal: 12, vertical: 5), ).padding(horizontal: 12, vertical: 5),
), ),
if (!Platform.isMacOS) if (!Platform.isMacOS)
@@ -179,9 +202,12 @@ class AppRootScaffold extends StatelessWidget {
Expanded(child: MoveWindow()), Expanded(child: MoveWindow()),
Row( Row(
children: [ children: [
MinimizeWindowButton(colors: windowButtonColor), MinimizeWindowButton(
MaximizeWindowButton(colors: windowButtonColor), colors: windowButtonColor),
CloseWindowButton(colors: windowButtonColor), MaximizeWindowButton(
colors: windowButtonColor),
CloseWindowButton(
colors: windowButtonColor),
], ],
), ),
], ],
@@ -194,16 +220,28 @@ class AppRootScaffold extends StatelessWidget {
Expanded(child: innerWidget), 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)) 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 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, drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
drawerEdgeDragWidth: isPopable ? 0 : 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(); State<NotifyIndicator> createState() => _NotifyIndicatorState();
} }
class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProviderStateMixin { class _NotifyIndicatorState extends State<NotifyIndicator>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController = AnimationController( late final AnimationController _animationController = AnimationController(
vsync: this, vsync: this,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@@ -101,7 +102,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
width: double.infinity, width: double.infinity,
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: isMobile ? MediaQuery.of(context).size.width - 16 : 360, maxWidth:
isMobile ? MediaQuery.of(context).size.width - 16 : 360,
), ),
child: Material( child: Material(
elevation: 2, elevation: 2,
@@ -118,7 +120,8 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
), ),
) )
else else
Icon(kNotificationTopicIcons[current?.topic] ?? Symbols.notifications), Icon(kNotificationTopicIcons[current?.topic] ??
Symbols.notifications),
const Gap(16), const Gap(16),
Expanded( Expanded(
child: Column( child: Column(
@@ -126,14 +129,20 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
children: [ children: [
Text( Text(
current?.title ?? 'Notification', current?.title ?? 'Notification',
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
if (current?.subtitle?.isNotEmpty ?? false) if (current?.subtitle?.isNotEmpty ?? false)
Text( Text(
current!.subtitle!, current!.subtitle!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -148,18 +157,25 @@ class _NotifyIndicatorState extends State<NotifyIndicator> with SingleTickerProv
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text(DateFormat('HH:mm').format(current?.createdAt.toLocal() ?? DateTime.now())) Text(
.fontSize(12) DateFormat('HH:mm').format(
.padding(right: 2), (current?.createdAt ?? DateTime.now())
.millisecondsSinceEpoch >
0
? (current?.createdAt ?? DateTime.now())
: DateTime.now()),
).fontSize(12).padding(right: 2),
const Gap(6), const Gap(6),
if (current?.metadata['image'] != null) if (current?.metadata['image'] != null)
SizedBox( SizedBox(
width: 40, width: 40,
height: 40, height: 40,
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(
Radius.circular(8)),
child: AutoResizeUniversalImage( child: AutoResizeUniversalImage(
sn.getAttachmentUrl(current?.metadata['image']), sn.getAttachmentUrl(
current?.metadata['image']),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),

View File

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

View File

@@ -9,9 +9,11 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_udid flutter_udid
flutter_webrtc flutter_webrtc
hotkey_manager_linux hotkey_manager_linux
local_notifier
media_kit_libs_linux media_kit_libs_linux
media_kit_video media_kit_video
pasteboard pasteboard
sqlite3_flutter_libs
tray_manager tray_manager
url_launcher_linux url_launcher_linux
) )

View File

@@ -21,6 +21,7 @@ import gal
import hotkey_manager_macos import hotkey_manager_macos
import in_app_review import in_app_review
import livekit_client import livekit_client
import local_notifier
import media_kit_libs_macos_video import media_kit_libs_macos_video
import media_kit_video import media_kit_video
import package_info_plus import package_info_plus
@@ -30,6 +31,7 @@ import screen_brightness_macos
import share_plus import share_plus
import shared_preferences_foundation import shared_preferences_foundation
import sqflite_darwin import sqflite_darwin
import sqlite3_flutter_libs
import tray_manager import tray_manager
import url_launcher_macos import url_launcher_macos
import video_compress import video_compress
@@ -52,6 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
@@ -61,6 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin")) VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))

View File

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

View File

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

View File

@@ -5,31 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "76.0.0" version: "80.0.0"
_flutterfire_internals: _flutterfire_internals:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.51" version: "1.3.52"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.11.0" version: "7.3.0"
animations: animations:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -50,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.3"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -66,10 +61,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.12.0"
bitsdojo_window: bitsdojo_window:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -114,10 +109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
build: build:
dependency: transitive dependency: transitive
description: description:
@@ -226,10 +221,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
@@ -250,10 +253,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
@@ -266,10 +269,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: collection name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.0" version: "1.19.1"
connectivity_plus: connectivity_plus:
dependency: transitive dependency: transitive
description: description:
@@ -346,18 +349,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.8" version: "3.0.1"
dart_webrtc: dart_webrtc:
dependency: "direct main" dependency: "direct main"
description: description:
name: dart_webrtc name: dart_webrtc
sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc" sha256: b34e90bc82f33c1023cf98661369c37bccd648c8a4cf882a875d9f5d8bbef694
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.2+hotfix.1"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@@ -414,6 +417,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
drift:
dependency: "direct main"
description:
name: drift
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb"
url: "https://pub.dev"
source: hosted
version: "2.25.1"
drift_dev:
dependency: "direct dev"
description:
name: drift_dev
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc
url: "https://pub.dev"
source: hosted
version: "2.25.2"
drift_flutter:
dependency: "direct main"
description:
name: drift_flutter
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922"
url: "https://pub.dev"
source: hosted
version: "0.2.4"
dropdown_button2: dropdown_button2:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -474,18 +501,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -498,10 +525,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.3.7" version: "9.0.0"
file_saver: file_saver:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -546,34 +573,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_analytics name: firebase_analytics
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2" sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.4.2" version: "11.4.3"
firebase_analytics_platform_interface: firebase_analytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_platform_interface name: firebase_analytics_platform_interface
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b" sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.2" version: "4.3.3"
firebase_analytics_web: firebase_analytics_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_analytics_web name: firebase_analytics_web
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef" sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.10+8" version: "0.5.10+9"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33" sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.11.0" version: "3.12.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -586,34 +613,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678" sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.20.0" version: "2.21.0"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
sha256: "3dee3b0cbfe719e64773cb7d1cad57c58b2235a8c136f5715fe733a54058c783" sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.2.2" version: "15.2.3"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
sha256: e9ea726b9bb864fc6223bb66422bd9877b9973ae51967754a769b0d01e201c1e sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.2" version: "4.6.3"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
sha256: "5f7b40e8bf861a37f8b8196e347d8a919750421a45f0b45d1bb74e98fa72726e" sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.10.2" version: "3.10.3"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -780,10 +807,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_native_splash name: flutter_native_splash
sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "2.4.5"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -838,18 +865,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_webrtc name: flutter_webrtc
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a" sha256: "6ea3a86d95b61cfe42d5715426d355b3cece6c88d0119de428d56f6c653811ce"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.9" version: "0.12.11"
freezed: freezed:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: freezed name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.7" version: "2.5.8"
freezed_annotation: freezed_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -914,30 +941,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" 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: home_widget:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1030,10 +1033,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.2" version: "4.5.3"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1046,10 +1049,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker_android name: image_picker_android
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+20" version: "0.8.12+21"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@@ -1150,26 +1153,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.9.0" version: "6.9.4"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.7" version: "10.0.8"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.8" version: "3.0.9"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@@ -1198,10 +1201,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: livekit_client name: livekit_client
sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52" sha256: "753bbf484c6b70f10f3dc1dc808dfe3755f472d80eb9682323cff07ad8e2609d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted 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: logging:
dependency: transitive dependency: transitive
description: description:
@@ -1210,14 +1221,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" 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: markdown:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1238,10 +1241,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -1342,10 +1345,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
mime: mime:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1414,10 +1417,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.1"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@@ -1486,26 +1489,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.3.1" version: "11.4.0"
permission_handler_android: permission_handler_android:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.13" version: "12.1.0"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_apple name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.4.5" version: "9.4.6"
permission_handler_html: permission_handler_html:
dependency: transitive dependency: transitive
description: description:
@@ -1518,10 +1521,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.3" version: "4.3.0"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
@@ -1534,10 +1537,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "6.1.0"
photo_view: photo_view:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1554,14 +1557,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.6" 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: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1642,6 +1637,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
recase:
dependency: transitive
description:
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev"
source: hosted
version: "4.1.0"
receive_sharing_intent: receive_sharing_intent:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1774,10 +1777,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16 sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5" version: "2.4.6"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@@ -1806,10 +1809,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.3"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@@ -1867,10 +1870,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.0" version: "2.0.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
@@ -1883,10 +1886,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.1"
sprintf: sprintf:
dependency: transitive dependency: transitive
description: description:
@@ -1899,34 +1902,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
sqflite_android: sqflite_android:
dependency: transitive dependency: transitive
description: description:
name: sqflite_android name: sqflite_android
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4+6" version: "2.5.5"
sqflite_darwin: sqflite_darwin:
dependency: transitive dependency: transitive
description: description:
name: sqflite_darwin name: sqflite_darwin
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1+1" version: "2.4.2"
sqflite_platform_interface: sqflite_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1935,22 +1938,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.0"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d"
url: "https://pub.dev"
source: hosted
version: "2.7.4"
sqlite3_flutter_libs:
dependency: transitive
description:
name: sqlite3_flutter_libs
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233"
url: "https://pub.dev"
source: hosted
version: "0.5.30"
sqlparser:
dependency: transitive
description:
name: sqlparser
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee"
url: "https://pub.dev"
source: hosted
version: "0.41.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
stream_transform: stream_transform:
dependency: transitive dependency: transitive
description: description:
@@ -1963,10 +1990,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.1"
styled_widget: styled_widget:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1987,26 +2014,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: synchronized name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0+3" version: "3.3.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3" version: "0.7.4"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@@ -2195,10 +2222,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.0" version: "14.3.1"
volume_controller: volume_controller:
dependency: transitive dependency: transitive
description: description:
@@ -2259,18 +2286,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webrtc_interface name: webrtc_interface
sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26" sha256: e05f00091c9c70a15bab4ccb1b6c46d9a16a6075002f02cfac3641eccb05e25d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1+hotfix.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.10.1" version: "5.11.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@@ -2312,5 +2339,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.6.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.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 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 2.3.2+70 version: 2.3.2+71
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -59,7 +59,7 @@ dependencies:
relative_time: ^5.0.0 relative_time: ^5.0.0
image_picker: ^1.1.2 image_picker: ^1.1.2
cross_file: ^0.3.4+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 croppy: ^1.3.1
flutter_expandable_fab: ^2.3.0 flutter_expandable_fab: ^2.3.0
dropdown_button2: ^2.3.9 dropdown_button2: ^2.3.9
@@ -72,8 +72,6 @@ dependencies:
collection: ^1.18.0 collection: ^1.18.0
mime: ^2.0.0 mime: ^2.0.0
web_socket_channel: ^3.0.1 web_socket_channel: ^3.0.1
hive: ^2.2.3
hive_flutter: ^1.1.0
swipe_to: ^1.0.6 swipe_to: ^1.0.6
firebase_core: ^3.8.0 firebase_core: ^3.8.0
firebase_messaging: ^15.1.5 firebase_messaging: ^15.1.5
@@ -123,6 +121,9 @@ dependencies:
image_picker_android: ^0.8.12+20 image_picker_android: ^0.8.12+20
cached_network_image_platform_interface: ^4.1.1 cached_network_image_platform_interface: ^4.1.1
image_picker_platform_interface: ^2.10.1 image_picker_platform_interface: ^2.10.1
drift: ^2.25.1
drift_flutter: ^0.2.4
local_notifier: ^0.1.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -134,13 +135,13 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
build_runner: ^2.4.13 build_runner: ^2.4.15
freezed: ^2.5.7 freezed: ^2.5.7
json_serializable: ^6.8.0 json_serializable: ^6.8.0
icons_launcher: ^3.0.0 icons_launcher: ^3.0.0
flutter_native_splash: ^2.4.2 flutter_native_splash: ^2.4.2
hive_generator: ^2.0.1
flutter_launcher_icons: ^0.14.1 flutter_launcher_icons: ^0.14.1
drift_dev: ^2.25.2
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

13631
web/drift_worker.dart.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,11 @@
{ {
"name": "surface", "name": "Solar Network",
"short_name": "surface", "short_name": "Solian",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#ffffff", "background_color": "#ffffff",
"theme_color": "#ffffff", "theme_color": "#ffffff",
"description": "A new Flutter project.", "description": "The Solar Network is a social network app.",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,
"icons": [ "icons": [

BIN
web/sqlite3.wasm Normal file

Binary file not shown.

View File

@@ -17,12 +17,14 @@
#include <gal/gal_plugin_c_api.h> #include <gal/gal_plugin_c_api.h>
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h> #include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
#include <livekit_client/live_kit_plugin.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_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h> #include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h> #include <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_brightness_windows/screen_brightness_windows_plugin.h> #include <screen_brightness_windows/screen_brightness_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <tray_manager/tray_manager_plugin.h> #include <tray_manager/tray_manager_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@@ -49,6 +51,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
LiveKitPluginRegisterWithRegistrar( LiveKitPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LiveKitPlugin")); registry->GetRegistrarForPlugin("LiveKitPlugin"));
LocalNotifierPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalNotifierPlugin"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
MediaKitVideoPluginCApiRegisterWithRegistrar( MediaKitVideoPluginCApiRegisterWithRegistrar(
@@ -61,6 +65,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar( SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
TrayManagerPluginRegisterWithRegistrar( TrayManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("TrayManagerPlugin")); registry->GetRegistrarForPlugin("TrayManagerPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(

View File

@@ -14,12 +14,14 @@ list(APPEND FLUTTER_PLUGIN_LIST
gal gal
hotkey_manager_windows hotkey_manager_windows
livekit_client livekit_client
local_notifier
media_kit_libs_windows_video media_kit_libs_windows_video
media_kit_video media_kit_video
pasteboard pasteboard
permission_handler_windows permission_handler_windows
screen_brightness_windows screen_brightness_windows
share_plus share_plus
sqlite3_flutter_libs
tray_manager tray_manager
url_launcher_windows url_launcher_windows
) )