Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e6c3f42f6 | |||
dc38b46b2c | |||
b4990308e9 | |||
237abe564d | |||
71b41d470a | |||
7052b5b635 | |||
f356e08f79 | |||
152872db65 | |||
dfe117d04f | |||
caf63f0cbe | |||
b8f5cc82f9 | |||
360bc50f21 | |||
2de93a0486 | |||
02227852f8 | |||
ad16de595b | |||
9f8c8923d9 | |||
060bfa4887 | |||
e68ada2d04 |
@ -130,7 +130,7 @@
|
|||||||
"accountPublishersSubtitle": "Manage your publish identities.",
|
"accountPublishersSubtitle": "Manage your publish identities.",
|
||||||
"accountSettings": "Account Settings",
|
"accountSettings": "Account Settings",
|
||||||
"accountSettingsSubtitle": "Manage your account and make it yours.",
|
"accountSettingsSubtitle": "Manage your account and make it yours.",
|
||||||
"accountProfileEdit": "Edit your profile",
|
"accountProfileEdit": "Edit Profile",
|
||||||
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
"accountProfileEditSubtitle": "Make your Solarpass account more looks like you.",
|
||||||
"accountWallet": "Wallet",
|
"accountWallet": "Wallet",
|
||||||
"accountWalletSubtitle": "View your balance and transactions.",
|
"accountWalletSubtitle": "View your balance and transactions.",
|
||||||
@ -338,6 +338,7 @@
|
|||||||
"fieldAttachmentRandomId": "Random ID",
|
"fieldAttachmentRandomId": "Random ID",
|
||||||
"fieldAttachmentAlt": "Alternative text",
|
"fieldAttachmentAlt": "Alternative text",
|
||||||
"addAttachmentFromAlbum": "Add from album",
|
"addAttachmentFromAlbum": "Add from album",
|
||||||
|
"addAttachmentFromFiles": "Add from files",
|
||||||
"addAttachmentFromClipboard": "Paste file",
|
"addAttachmentFromClipboard": "Paste file",
|
||||||
"addAttachmentFromCameraPhoto": "Take photo",
|
"addAttachmentFromCameraPhoto": "Take photo",
|
||||||
"addAttachmentFromCameraVideo": "Take video",
|
"addAttachmentFromCameraVideo": "Take video",
|
||||||
@ -846,5 +847,48 @@
|
|||||||
"translating": "Translating…",
|
"translating": "Translating…",
|
||||||
"translated": "Translated",
|
"translated": "Translated",
|
||||||
"settingsAutoTranslate": "Auto Translate",
|
"settingsAutoTranslate": "Auto Translate",
|
||||||
"settingsAutoTranslateDescription": "Automatically translate text when viewing posts and messages."
|
"settingsAutoTranslateDescription": "Automatically translate text when viewing posts and messages.",
|
||||||
|
"trayMenuHide": "Hide",
|
||||||
|
"accountSettingsNotify": "Notify Settings",
|
||||||
|
"accountSettingsNotifyDescription": "Adjust the types of notifications you receive.",
|
||||||
|
"accountSettingsSecurity": "Security Settings",
|
||||||
|
"accountSettingsSecurityDescription": "Adjust your account security settings.",
|
||||||
|
"save": "Save",
|
||||||
|
"notificationTopicPostFeedback": "Post Feedback",
|
||||||
|
"notificationTopicPostReply": "Post Replies",
|
||||||
|
"notificationTopicPostSubscription": "Post Subscriptions",
|
||||||
|
"notificationTopicMessaging": "New Messages",
|
||||||
|
"notificationTopicMessagingCall": "Incoming Calls",
|
||||||
|
"notificationTopicGeneral": "General",
|
||||||
|
"authMaximumAuthSteps": "Maximum Authenticate Steps",
|
||||||
|
"authMaximumAuthStepsDescription": {
|
||||||
|
"one": "Maximum ask for {} step authenticate",
|
||||||
|
"other": "Maximum ask for {} steps authenticate"
|
||||||
|
},
|
||||||
|
"authAlwaysRisky": "Always Risky",
|
||||||
|
"authAlwaysRiskyDescription": "Always ask for the highest steps count of authentication when logging in.",
|
||||||
|
"chatUnjoined": "Unjoined Channel",
|
||||||
|
"chatUnjoinedDescription": "You haven't joined this channel, so you can't send messages either view messages in it.",
|
||||||
|
"chatUnjoinedPublicDescription": "Fortunately, this is a public channel, so you can join it as you want.",
|
||||||
|
"chatJoin": "Join the Channel",
|
||||||
|
"appInitStarting": "Starting",
|
||||||
|
"appInitNetwork": "Initializing Network",
|
||||||
|
"appInitUserdata": "Initializing User Data",
|
||||||
|
"appInitWebsocket": "Establishing Solar Link",
|
||||||
|
"appInitNotification": "Initializing Push Notifications",
|
||||||
|
"appInitKeyPair": "Initializing Key Pairs",
|
||||||
|
"appInitStickers": "Initializing Stickers",
|
||||||
|
"appInitUserDirectory": "Initializing User Directory",
|
||||||
|
"appInitRealm": "Initializing Realms",
|
||||||
|
"appInitChat": "Initializing Chat",
|
||||||
|
"appInitDone": "Completed",
|
||||||
|
"community": "Community",
|
||||||
|
"realmCommunity": "{}'s Community",
|
||||||
|
"postTotalCount": {
|
||||||
|
"one": "Total {} post",
|
||||||
|
"other": "Total {} posts"
|
||||||
|
},
|
||||||
|
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||||
|
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer.",
|
||||||
|
"reCaptcha": "reCaptcha"
|
||||||
}
|
}
|
||||||
|
@ -336,6 +336,7 @@
|
|||||||
"fieldAttachmentRandomId": "访问 ID",
|
"fieldAttachmentRandomId": "访问 ID",
|
||||||
"fieldAttachmentAlt": "概述文字",
|
"fieldAttachmentAlt": "概述文字",
|
||||||
"addAttachmentFromAlbum": "从相册中添加附件",
|
"addAttachmentFromAlbum": "从相册中添加附件",
|
||||||
|
"addAttachmentFromFiles": "从文件中添加附件",
|
||||||
"addAttachmentFromClipboard": "粘贴附件",
|
"addAttachmentFromClipboard": "粘贴附件",
|
||||||
"addAttachmentFromCameraPhoto": "拍摄照片",
|
"addAttachmentFromCameraPhoto": "拍摄照片",
|
||||||
"addAttachmentFromCameraVideo": "拍摄视频",
|
"addAttachmentFromCameraVideo": "拍摄视频",
|
||||||
@ -844,5 +845,48 @@
|
|||||||
"translating": "正在翻译……",
|
"translating": "正在翻译……",
|
||||||
"translated": "已翻译",
|
"translated": "已翻译",
|
||||||
"settingsAutoTranslate": "自动翻译",
|
"settingsAutoTranslate": "自动翻译",
|
||||||
"settingsAutoTranslateDescription": "在查看帖子、消息时自动翻译文本。"
|
"settingsAutoTranslateDescription": "在查看帖子、消息时自动翻译文本。",
|
||||||
|
"trayMenuHide": "隐藏",
|
||||||
|
"accountSettingsNotify": "通知设置",
|
||||||
|
"accountSettingsNotifyDescription": "调整你所收到的通知种类。",
|
||||||
|
"accountSettingsSecurity": "安全设置",
|
||||||
|
"accountSettingsSecurityDescription": "调整你的帐户安全设置。",
|
||||||
|
"save": "保存",
|
||||||
|
"notificationTopicPostFeedback": "帖子数据反馈",
|
||||||
|
"notificationTopicPostReply": "帖子回复",
|
||||||
|
"notificationTopicPostSubscription": "帖子订阅",
|
||||||
|
"notificationTopicMessaging": "消息",
|
||||||
|
"notificationTopicMessagingCall": "通话",
|
||||||
|
"notificationTopicGeneral": "杂项",
|
||||||
|
"authMaximumAuthSteps": "最大验证步骤",
|
||||||
|
"authMaximumAuthStepsDescription": {
|
||||||
|
"one": "登入时最多要求 {} 步验证",
|
||||||
|
"other": "登入时最多要求 {} 步验证"
|
||||||
|
},
|
||||||
|
"authAlwaysRisky": "总是风险",
|
||||||
|
"authAlwaysRiskyDescription": "在登入时始终按最高标准要求验证。",
|
||||||
|
"chatUnjoined": "未加入频道",
|
||||||
|
"chatUnjoinedDescription": "你没有加入这个频道,所以你也无法发送消息或者查看这个频道中的消息。",
|
||||||
|
"chatUnjoinedPublicDescription": "但幸运的是,这是一个公开频道,所以你可以主动加入。",
|
||||||
|
"chatJoin": "加入频道",
|
||||||
|
"appInitStarting": "启动中",
|
||||||
|
"appInitNetwork": "正在初始化网络",
|
||||||
|
"appInitUserdata": "正在初始化用户数据",
|
||||||
|
"appInitWebsocket": "正在建立 Solar Link",
|
||||||
|
"appInitNotification": "正在初始化推送通知",
|
||||||
|
"appInitKeyPair": "正在初始化密钥对",
|
||||||
|
"appInitStickers": "正在初始化贴图包",
|
||||||
|
"appInitUserDirectory": "正在初始化用户目录",
|
||||||
|
"appInitRealm": "正在初始化领域信息",
|
||||||
|
"appInitChat": "正在初始化聊天",
|
||||||
|
"appInitDone": "完成",
|
||||||
|
"community": "社区",
|
||||||
|
"realmCommunity": "{}的社区",
|
||||||
|
"postTotalCount": {
|
||||||
|
"zero": "没有帖子",
|
||||||
|
"one": "共 {} 条帖子"
|
||||||
|
},
|
||||||
|
"settingsHideBottomNav": "隐藏底部导航栏",
|
||||||
|
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。",
|
||||||
|
"reCaptcha": "人机验证"
|
||||||
}
|
}
|
||||||
|
@ -336,6 +336,7 @@
|
|||||||
"fieldAttachmentRandomId": "訪問 ID",
|
"fieldAttachmentRandomId": "訪問 ID",
|
||||||
"fieldAttachmentAlt": "概述文字",
|
"fieldAttachmentAlt": "概述文字",
|
||||||
"addAttachmentFromAlbum": "從相冊中添加附件",
|
"addAttachmentFromAlbum": "從相冊中添加附件",
|
||||||
|
"addAttachmentFromFiles": "從文件中添加附件",
|
||||||
"addAttachmentFromClipboard": "粘貼附件",
|
"addAttachmentFromClipboard": "粘貼附件",
|
||||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||||
@ -844,5 +845,48 @@
|
|||||||
"translating": "正在翻譯……",
|
"translating": "正在翻譯……",
|
||||||
"translated": "已翻譯",
|
"translated": "已翻譯",
|
||||||
"settingsAutoTranslate": "自動翻譯",
|
"settingsAutoTranslate": "自動翻譯",
|
||||||
"settingsAutoTranslateDescription": "在查看帖子、消息時自動翻譯文本。"
|
"settingsAutoTranslateDescription": "在查看帖子、消息時自動翻譯文本。",
|
||||||
|
"trayMenuHide": "隱藏",
|
||||||
|
"accountSettingsNotify": "通知設置",
|
||||||
|
"accountSettingsNotifyDescription": "調整你所收到的通知種類。",
|
||||||
|
"accountSettingsSecurity": "安全設置",
|
||||||
|
"accountSettingsSecurityDescription": "調整你的帳户安全設置。",
|
||||||
|
"save": "保存",
|
||||||
|
"notificationTopicPostFeedback": "帖子數據反饋",
|
||||||
|
"notificationTopicPostReply": "帖子回覆",
|
||||||
|
"notificationTopicPostSubscription": "帖子訂閲",
|
||||||
|
"notificationTopicMessaging": "消息",
|
||||||
|
"notificationTopicMessagingCall": "通話",
|
||||||
|
"notificationTopicGeneral": "雜項",
|
||||||
|
"authMaximumAuthSteps": "最大驗證步驟",
|
||||||
|
"authMaximumAuthStepsDescription": {
|
||||||
|
"one": "登入時最多要求 {} 步驗證",
|
||||||
|
"other": "登入時最多要求 {} 步驗證"
|
||||||
|
},
|
||||||
|
"authAlwaysRisky": "總是風險",
|
||||||
|
"authAlwaysRiskyDescription": "在登入時始終按最高標準要求驗證。",
|
||||||
|
"chatUnjoined": "未加入頻道",
|
||||||
|
"chatUnjoinedDescription": "你沒有加入這個頻道,所以你也無法發送消息或者查看這個頻道中的消息。",
|
||||||
|
"chatUnjoinedPublicDescription": "但幸運的是,這是一個公開頻道,所以你可以主動加入。",
|
||||||
|
"chatJoin": "加入頻道",
|
||||||
|
"appInitStarting": "啓動中",
|
||||||
|
"appInitNetwork": "正在初始化網絡",
|
||||||
|
"appInitUserdata": "正在初始化用户數據",
|
||||||
|
"appInitWebsocket": "正在建立 Solar Link",
|
||||||
|
"appInitNotification": "正在初始化推送通知",
|
||||||
|
"appInitKeyPair": "正在初始化密鑰對",
|
||||||
|
"appInitStickers": "正在初始化貼圖包",
|
||||||
|
"appInitUserDirectory": "正在初始化用户目錄",
|
||||||
|
"appInitRealm": "正在初始化領域信息",
|
||||||
|
"appInitChat": "正在初始化聊天",
|
||||||
|
"appInitDone": "完成",
|
||||||
|
"community": "社區",
|
||||||
|
"realmCommunity": "{}的社區",
|
||||||
|
"postTotalCount": {
|
||||||
|
"zero": "沒有帖子",
|
||||||
|
"one": "共 {} 條帖子"
|
||||||
|
},
|
||||||
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
|
"reCaptcha": "人機驗證"
|
||||||
}
|
}
|
||||||
|
@ -336,6 +336,7 @@
|
|||||||
"fieldAttachmentRandomId": "訪問 ID",
|
"fieldAttachmentRandomId": "訪問 ID",
|
||||||
"fieldAttachmentAlt": "概述文字",
|
"fieldAttachmentAlt": "概述文字",
|
||||||
"addAttachmentFromAlbum": "從相冊中添加附件",
|
"addAttachmentFromAlbum": "從相冊中添加附件",
|
||||||
|
"addAttachmentFromFiles": "從文件中添加附件",
|
||||||
"addAttachmentFromClipboard": "粘貼附件",
|
"addAttachmentFromClipboard": "粘貼附件",
|
||||||
"addAttachmentFromCameraPhoto": "拍攝照片",
|
"addAttachmentFromCameraPhoto": "拍攝照片",
|
||||||
"addAttachmentFromCameraVideo": "拍攝視頻",
|
"addAttachmentFromCameraVideo": "拍攝視頻",
|
||||||
@ -844,5 +845,48 @@
|
|||||||
"translating": "正在翻譯……",
|
"translating": "正在翻譯……",
|
||||||
"translated": "已翻譯",
|
"translated": "已翻譯",
|
||||||
"settingsAutoTranslate": "自動翻譯",
|
"settingsAutoTranslate": "自動翻譯",
|
||||||
"settingsAutoTranslateDescription": "在查看帖子、消息時自動翻譯文本。"
|
"settingsAutoTranslateDescription": "在查看帖子、消息時自動翻譯文本。",
|
||||||
|
"trayMenuHide": "隱藏",
|
||||||
|
"accountSettingsNotify": "通知設置",
|
||||||
|
"accountSettingsNotifyDescription": "調整你所收到的通知種類。",
|
||||||
|
"accountSettingsSecurity": "安全設置",
|
||||||
|
"accountSettingsSecurityDescription": "調整你的帳戶安全設置。",
|
||||||
|
"save": "保存",
|
||||||
|
"notificationTopicPostFeedback": "帖子數據反饋",
|
||||||
|
"notificationTopicPostReply": "帖子回覆",
|
||||||
|
"notificationTopicPostSubscription": "帖子訂閱",
|
||||||
|
"notificationTopicMessaging": "消息",
|
||||||
|
"notificationTopicMessagingCall": "通話",
|
||||||
|
"notificationTopicGeneral": "雜項",
|
||||||
|
"authMaximumAuthSteps": "最大驗證步驟",
|
||||||
|
"authMaximumAuthStepsDescription": {
|
||||||
|
"one": "登入時最多要求 {} 步驗證",
|
||||||
|
"other": "登入時最多要求 {} 步驗證"
|
||||||
|
},
|
||||||
|
"authAlwaysRisky": "總是風險",
|
||||||
|
"authAlwaysRiskyDescription": "在登入時始終按最高標準要求驗證。",
|
||||||
|
"chatUnjoined": "未加入頻道",
|
||||||
|
"chatUnjoinedDescription": "你沒有加入這個頻道,所以你也無法發送消息或者查看這個頻道中的消息。",
|
||||||
|
"chatUnjoinedPublicDescription": "但幸運的是,這是一個公開頻道,所以你可以主動加入。",
|
||||||
|
"chatJoin": "加入頻道",
|
||||||
|
"appInitStarting": "啟動中",
|
||||||
|
"appInitNetwork": "正在初始化網絡",
|
||||||
|
"appInitUserdata": "正在初始化用戶數據",
|
||||||
|
"appInitWebsocket": "正在建立 Solar Link",
|
||||||
|
"appInitNotification": "正在初始化推送通知",
|
||||||
|
"appInitKeyPair": "正在初始化密鑰對",
|
||||||
|
"appInitStickers": "正在初始化貼圖包",
|
||||||
|
"appInitUserDirectory": "正在初始化用戶目錄",
|
||||||
|
"appInitRealm": "正在初始化領域信息",
|
||||||
|
"appInitChat": "正在初始化聊天",
|
||||||
|
"appInitDone": "完成",
|
||||||
|
"community": "社區",
|
||||||
|
"realmCommunity": "{}的社區",
|
||||||
|
"postTotalCount": {
|
||||||
|
"zero": "沒有帖子",
|
||||||
|
"one": "共 {} 條帖子"
|
||||||
|
},
|
||||||
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
|
"reCaptcha": "人機驗證"
|
||||||
}
|
}
|
||||||
|
1
drift_schemas/my_database/drift_schema_v4.json
Normal file
1
drift_schemas/my_database/drift_schema_v4.json
Normal file
File diff suppressed because one or more lines are too long
@ -232,6 +232,8 @@ PODS:
|
|||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.49.1):
|
- sqlite3/fts5 (3.49.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
|
- sqlite3/math (3.49.1):
|
||||||
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.49.1):
|
- sqlite3/perf-threadsafe (3.49.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.49.1):
|
- sqlite3/rtree (3.49.1):
|
||||||
@ -242,6 +244,7 @@ PODS:
|
|||||||
- sqlite3 (~> 3.49.1)
|
- sqlite3 (~> 3.49.1)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
@ -457,7 +460,7 @@ SPEC CHECKSUMS:
|
|||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||||
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
|
sqlite3_flutter_libs: 487032b9008b28de37c72a3aa66849ef3745f3e6
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
||||||
|
@ -79,6 +79,8 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
@ -6,10 +6,12 @@ import 'package:surface/database/attachment.dart';
|
|||||||
import 'package:surface/database/chat.dart';
|
import 'package:surface/database/chat.dart';
|
||||||
import 'package:surface/database/database.steps.dart';
|
import 'package:surface/database/database.steps.dart';
|
||||||
import 'package:surface/database/keypair.dart';
|
import 'package:surface/database/keypair.dart';
|
||||||
|
import 'package:surface/database/realm.dart';
|
||||||
import 'package:surface/database/sticker.dart';
|
import 'package:surface/database/sticker.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
||||||
@ -22,12 +24,13 @@ part 'database.g.dart';
|
|||||||
SnLocalAttachment,
|
SnLocalAttachment,
|
||||||
SnLocalSticker,
|
SnLocalSticker,
|
||||||
SnLocalStickerPack,
|
SnLocalStickerPack,
|
||||||
|
SnLocalRealm,
|
||||||
])
|
])
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase([QueryExecutor? e]) : super(e ?? _openConnection());
|
AppDatabase([QueryExecutor? e]) : super(e ?? _openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 3;
|
int get schemaVersion => 4;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
@ -49,6 +52,10 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
// Nothing else to do here
|
// Nothing else to do here
|
||||||
}, from2To3: (m, schema) async {
|
}, from2To3: (m, schema) async {
|
||||||
// Nothing else to do here, too
|
// Nothing else to do here, too
|
||||||
|
}, from3To4: (m, schema) async {
|
||||||
|
m.createTable(schema.snLocalRealm);
|
||||||
|
m.createIndex(schema.idxRealmAccount);
|
||||||
|
m.createIndex(schema.idxRealmAlias);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2454,6 +2454,351 @@ class SnLocalStickerPackCompanion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class $SnLocalRealmTable extends SnLocalRealm
|
||||||
|
with TableInfo<$SnLocalRealmTable, SnLocalRealmData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$SnLocalRealmTable(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,
|
||||||
|
defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'));
|
||||||
|
@override
|
||||||
|
late final GeneratedColumnWithTypeConverter<SnRealm, String> content =
|
||||||
|
GeneratedColumn<String>('content', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||||
|
.withConverter<SnRealm>($SnLocalRealmTable.$convertercontent);
|
||||||
|
static const VerificationMeta _accountIdMeta =
|
||||||
|
const VerificationMeta('accountId');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<int> accountId = GeneratedColumn<int>(
|
||||||
|
'account_id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||||
|
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);
|
||||||
|
static const VerificationMeta _cacheExpiredAtMeta =
|
||||||
|
const VerificationMeta('cacheExpiredAt');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> cacheExpiredAt =
|
||||||
|
GeneratedColumn<DateTime>('cache_expired_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime, requiredDuringInsert: true);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns =>
|
||||||
|
[id, alias, content, accountId, createdAt, cacheExpiredAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_realm';
|
||||||
|
@override
|
||||||
|
VerificationContext validateIntegrity(Insertable<SnLocalRealmData> 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);
|
||||||
|
}
|
||||||
|
if (data.containsKey('account_id')) {
|
||||||
|
context.handle(_accountIdMeta,
|
||||||
|
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_accountIdMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('created_at')) {
|
||||||
|
context.handle(_createdAtMeta,
|
||||||
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('cache_expired_at')) {
|
||||||
|
context.handle(
|
||||||
|
_cacheExpiredAtMeta,
|
||||||
|
cacheExpiredAt.isAcceptableOrUnknown(
|
||||||
|
data['cache_expired_at']!, _cacheExpiredAtMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_cacheExpiredAtMeta);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
SnLocalRealmData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalRealmData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
alias: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}alias'])!,
|
||||||
|
content: $SnLocalRealmTable.$convertercontent.fromSql(attachedDatabase
|
||||||
|
.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}content'])!),
|
||||||
|
accountId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}account_id'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
cacheExpiredAt: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.dateTime, data['${effectivePrefix}cache_expired_at'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnLocalRealmTable createAlias(String alias) {
|
||||||
|
return $SnLocalRealmTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsonTypeConverter2<SnRealm, String, Map<String, Object?>>
|
||||||
|
$convertercontent = const SnRealmConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalRealmData extends DataClass
|
||||||
|
implements Insertable<SnLocalRealmData> {
|
||||||
|
final int id;
|
||||||
|
final String alias;
|
||||||
|
final SnRealm content;
|
||||||
|
final int accountId;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime cacheExpiredAt;
|
||||||
|
const SnLocalRealmData(
|
||||||
|
{required this.id,
|
||||||
|
required this.alias,
|
||||||
|
required this.content,
|
||||||
|
required this.accountId,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.cacheExpiredAt});
|
||||||
|
@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>($SnLocalRealmTable.$convertercontent.toSql(content));
|
||||||
|
}
|
||||||
|
map['account_id'] = Variable<int>(accountId);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
map['cache_expired_at'] = Variable<DateTime>(cacheExpiredAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalRealmCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalRealmCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
alias: Value(alias),
|
||||||
|
content: Value(content),
|
||||||
|
accountId: Value(accountId),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
cacheExpiredAt: Value(cacheExpiredAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalRealmData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalRealmData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
alias: serializer.fromJson<String>(json['alias']),
|
||||||
|
content: $SnLocalRealmTable.$convertercontent
|
||||||
|
.fromJson(serializer.fromJson<Map<String, Object?>>(json['content'])),
|
||||||
|
accountId: serializer.fromJson<int>(json['accountId']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
cacheExpiredAt: serializer.fromJson<DateTime>(json['cacheExpiredAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@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?>>(
|
||||||
|
$SnLocalRealmTable.$convertercontent.toJson(content)),
|
||||||
|
'accountId': serializer.toJson<int>(accountId),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
'cacheExpiredAt': serializer.toJson<DateTime>(cacheExpiredAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalRealmData copyWith(
|
||||||
|
{int? id,
|
||||||
|
String? alias,
|
||||||
|
SnRealm? content,
|
||||||
|
int? accountId,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? cacheExpiredAt}) =>
|
||||||
|
SnLocalRealmData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
cacheExpiredAt: cacheExpiredAt ?? this.cacheExpiredAt,
|
||||||
|
);
|
||||||
|
SnLocalRealmData copyWithCompanion(SnLocalRealmCompanion data) {
|
||||||
|
return SnLocalRealmData(
|
||||||
|
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,
|
||||||
|
accountId: data.accountId.present ? data.accountId.value : this.accountId,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
cacheExpiredAt: data.cacheExpiredAt.present
|
||||||
|
? data.cacheExpiredAt.value
|
||||||
|
: this.cacheExpiredAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalRealmData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('cacheExpiredAt: $cacheExpiredAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(id, alias, content, accountId, createdAt, cacheExpiredAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalRealmData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.alias == this.alias &&
|
||||||
|
other.content == this.content &&
|
||||||
|
other.accountId == this.accountId &&
|
||||||
|
other.createdAt == this.createdAt &&
|
||||||
|
other.cacheExpiredAt == this.cacheExpiredAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalRealmCompanion extends UpdateCompanion<SnLocalRealmData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<String> alias;
|
||||||
|
final Value<SnRealm> content;
|
||||||
|
final Value<int> accountId;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
final Value<DateTime> cacheExpiredAt;
|
||||||
|
const SnLocalRealmCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.alias = const Value.absent(),
|
||||||
|
this.content = const Value.absent(),
|
||||||
|
this.accountId = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
this.cacheExpiredAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalRealmCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required String alias,
|
||||||
|
required SnRealm content,
|
||||||
|
required int accountId,
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
required DateTime cacheExpiredAt,
|
||||||
|
}) : alias = Value(alias),
|
||||||
|
content = Value(content),
|
||||||
|
accountId = Value(accountId),
|
||||||
|
cacheExpiredAt = Value(cacheExpiredAt);
|
||||||
|
static Insertable<SnLocalRealmData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<String>? alias,
|
||||||
|
Expression<String>? content,
|
||||||
|
Expression<int>? accountId,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
Expression<DateTime>? cacheExpiredAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (alias != null) 'alias': alias,
|
||||||
|
if (content != null) 'content': content,
|
||||||
|
if (accountId != null) 'account_id': accountId,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (cacheExpiredAt != null) 'cache_expired_at': cacheExpiredAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalRealmCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<String>? alias,
|
||||||
|
Value<SnRealm>? content,
|
||||||
|
Value<int>? accountId,
|
||||||
|
Value<DateTime>? createdAt,
|
||||||
|
Value<DateTime>? cacheExpiredAt}) {
|
||||||
|
return SnLocalRealmCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
cacheExpiredAt: cacheExpiredAt ?? this.cacheExpiredAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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>(
|
||||||
|
$SnLocalRealmTable.$convertercontent.toSql(content.value));
|
||||||
|
}
|
||||||
|
if (accountId.present) {
|
||||||
|
map['account_id'] = Variable<int>(accountId.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
if (cacheExpiredAt.present) {
|
||||||
|
map['cache_expired_at'] = Variable<DateTime>(cacheExpiredAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalRealmCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('cacheExpiredAt: $cacheExpiredAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class _$AppDatabase extends GeneratedDatabase {
|
abstract class _$AppDatabase extends GeneratedDatabase {
|
||||||
_$AppDatabase(QueryExecutor e) : super(e);
|
_$AppDatabase(QueryExecutor e) : super(e);
|
||||||
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
||||||
@ -2470,6 +2815,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
late final $SnLocalStickerTable snLocalSticker = $SnLocalStickerTable(this);
|
late final $SnLocalStickerTable snLocalSticker = $SnLocalStickerTable(this);
|
||||||
late final $SnLocalStickerPackTable snLocalStickerPack =
|
late final $SnLocalStickerPackTable snLocalStickerPack =
|
||||||
$SnLocalStickerPackTable(this);
|
$SnLocalStickerPackTable(this);
|
||||||
|
late final $SnLocalRealmTable snLocalRealm = $SnLocalRealmTable(this);
|
||||||
late final Index idxChannelAlias = Index('idx_channel_alias',
|
late final Index idxChannelAlias = Index('idx_channel_alias',
|
||||||
'CREATE INDEX idx_channel_alias ON sn_local_chat_channel (alias)');
|
'CREATE INDEX idx_channel_alias ON sn_local_chat_channel (alias)');
|
||||||
late final Index idxChatChannel = Index('idx_chat_channel',
|
late final Index idxChatChannel = Index('idx_chat_channel',
|
||||||
@ -2480,6 +2826,10 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
'CREATE INDEX idx_attachment_rid ON sn_local_attachment (rid)');
|
'CREATE INDEX idx_attachment_rid ON sn_local_attachment (rid)');
|
||||||
late final Index idxAttachmentAccount = Index('idx_attachment_account',
|
late final Index idxAttachmentAccount = Index('idx_attachment_account',
|
||||||
'CREATE INDEX idx_attachment_account ON sn_local_attachment (account_id)');
|
'CREATE INDEX idx_attachment_account ON sn_local_attachment (account_id)');
|
||||||
|
late final Index idxRealmAlias = Index('idx_realm_alias',
|
||||||
|
'CREATE INDEX idx_realm_alias ON sn_local_realm (alias)');
|
||||||
|
late final Index idxRealmAccount = Index('idx_realm_account',
|
||||||
|
'CREATE INDEX idx_realm_account ON sn_local_realm (account_id)');
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@ -2493,11 +2843,14 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
snLocalAttachment,
|
snLocalAttachment,
|
||||||
snLocalSticker,
|
snLocalSticker,
|
||||||
snLocalStickerPack,
|
snLocalStickerPack,
|
||||||
|
snLocalRealm,
|
||||||
idxChannelAlias,
|
idxChannelAlias,
|
||||||
idxChatChannel,
|
idxChatChannel,
|
||||||
idxAccountName,
|
idxAccountName,
|
||||||
idxAttachmentRid,
|
idxAttachmentRid,
|
||||||
idxAttachmentAccount
|
idxAttachmentAccount,
|
||||||
|
idxRealmAlias,
|
||||||
|
idxRealmAccount
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3888,6 +4241,192 @@ typedef $$SnLocalStickerPackTableProcessedTableManager = ProcessedTableManager<
|
|||||||
),
|
),
|
||||||
SnLocalStickerPackData,
|
SnLocalStickerPackData,
|
||||||
PrefetchHooks Function()>;
|
PrefetchHooks Function()>;
|
||||||
|
typedef $$SnLocalRealmTableCreateCompanionBuilder = SnLocalRealmCompanion
|
||||||
|
Function({
|
||||||
|
Value<int> id,
|
||||||
|
required String alias,
|
||||||
|
required SnRealm content,
|
||||||
|
required int accountId,
|
||||||
|
Value<DateTime> createdAt,
|
||||||
|
required DateTime cacheExpiredAt,
|
||||||
|
});
|
||||||
|
typedef $$SnLocalRealmTableUpdateCompanionBuilder = SnLocalRealmCompanion
|
||||||
|
Function({
|
||||||
|
Value<int> id,
|
||||||
|
Value<String> alias,
|
||||||
|
Value<SnRealm> content,
|
||||||
|
Value<int> accountId,
|
||||||
|
Value<DateTime> createdAt,
|
||||||
|
Value<DateTime> cacheExpiredAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$SnLocalRealmTableFilterComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalRealmTable> {
|
||||||
|
$$SnLocalRealmTableFilterComposer({
|
||||||
|
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<SnRealm, SnRealm, String> get content =>
|
||||||
|
$composableBuilder(
|
||||||
|
column: $table.content,
|
||||||
|
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<int> get accountId => $composableBuilder(
|
||||||
|
column: $table.accountId, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get cacheExpiredAt => $composableBuilder(
|
||||||
|
column: $table.cacheExpiredAt,
|
||||||
|
builder: (column) => ColumnFilters(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalRealmTableOrderingComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalRealmTable> {
|
||||||
|
$$SnLocalRealmTableOrderingComposer({
|
||||||
|
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<int> get accountId => $composableBuilder(
|
||||||
|
column: $table.accountId, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get cacheExpiredAt => $composableBuilder(
|
||||||
|
column: $table.cacheExpiredAt,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalRealmTableAnnotationComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalRealmTable> {
|
||||||
|
$$SnLocalRealmTableAnnotationComposer({
|
||||||
|
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<SnRealm, String> get content =>
|
||||||
|
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get accountId =>
|
||||||
|
$composableBuilder(column: $table.accountId, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get cacheExpiredAt => $composableBuilder(
|
||||||
|
column: $table.cacheExpiredAt, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalRealmTableTableManager extends RootTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$SnLocalRealmTable,
|
||||||
|
SnLocalRealmData,
|
||||||
|
$$SnLocalRealmTableFilterComposer,
|
||||||
|
$$SnLocalRealmTableOrderingComposer,
|
||||||
|
$$SnLocalRealmTableAnnotationComposer,
|
||||||
|
$$SnLocalRealmTableCreateCompanionBuilder,
|
||||||
|
$$SnLocalRealmTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
SnLocalRealmData,
|
||||||
|
BaseReferences<_$AppDatabase, $SnLocalRealmTable, SnLocalRealmData>
|
||||||
|
),
|
||||||
|
SnLocalRealmData,
|
||||||
|
PrefetchHooks Function()> {
|
||||||
|
$$SnLocalRealmTableTableManager(_$AppDatabase db, $SnLocalRealmTable table)
|
||||||
|
: super(TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
$$SnLocalRealmTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
$$SnLocalRealmTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
$$SnLocalRealmTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback: ({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
Value<String> alias = const Value.absent(),
|
||||||
|
Value<SnRealm> content = const Value.absent(),
|
||||||
|
Value<int> accountId = const Value.absent(),
|
||||||
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
Value<DateTime> cacheExpiredAt = const Value.absent(),
|
||||||
|
}) =>
|
||||||
|
SnLocalRealmCompanion(
|
||||||
|
id: id,
|
||||||
|
alias: alias,
|
||||||
|
content: content,
|
||||||
|
accountId: accountId,
|
||||||
|
createdAt: createdAt,
|
||||||
|
cacheExpiredAt: cacheExpiredAt,
|
||||||
|
),
|
||||||
|
createCompanionCallback: ({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
required String alias,
|
||||||
|
required SnRealm content,
|
||||||
|
required int accountId,
|
||||||
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
required DateTime cacheExpiredAt,
|
||||||
|
}) =>
|
||||||
|
SnLocalRealmCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
alias: alias,
|
||||||
|
content: content,
|
||||||
|
accountId: accountId,
|
||||||
|
createdAt: createdAt,
|
||||||
|
cacheExpiredAt: cacheExpiredAt,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$SnLocalRealmTableProcessedTableManager = ProcessedTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$SnLocalRealmTable,
|
||||||
|
SnLocalRealmData,
|
||||||
|
$$SnLocalRealmTableFilterComposer,
|
||||||
|
$$SnLocalRealmTableOrderingComposer,
|
||||||
|
$$SnLocalRealmTableAnnotationComposer,
|
||||||
|
$$SnLocalRealmTableCreateCompanionBuilder,
|
||||||
|
$$SnLocalRealmTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
SnLocalRealmData,
|
||||||
|
BaseReferences<_$AppDatabase, $SnLocalRealmTable, SnLocalRealmData>
|
||||||
|
),
|
||||||
|
SnLocalRealmData,
|
||||||
|
PrefetchHooks Function()>;
|
||||||
|
|
||||||
class $AppDatabaseManager {
|
class $AppDatabaseManager {
|
||||||
final _$AppDatabase _db;
|
final _$AppDatabase _db;
|
||||||
@ -3908,4 +4447,6 @@ class $AppDatabaseManager {
|
|||||||
$$SnLocalStickerTableTableManager(_db, _db.snLocalSticker);
|
$$SnLocalStickerTableTableManager(_db, _db.snLocalSticker);
|
||||||
$$SnLocalStickerPackTableTableManager get snLocalStickerPack =>
|
$$SnLocalStickerPackTableTableManager get snLocalStickerPack =>
|
||||||
$$SnLocalStickerPackTableTableManager(_db, _db.snLocalStickerPack);
|
$$SnLocalStickerPackTableTableManager(_db, _db.snLocalStickerPack);
|
||||||
|
$$SnLocalRealmTableTableManager get snLocalRealm =>
|
||||||
|
$$SnLocalRealmTableTableManager(_db, _db.snLocalRealm);
|
||||||
}
|
}
|
||||||
|
@ -412,9 +412,214 @@ class Shape8 extends i0.VersionedTable {
|
|||||||
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema4 extends i0.VersionedSchema {
|
||||||
|
Schema4({required super.database}) : super(version: 4);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
snLocalChatChannel,
|
||||||
|
snLocalChatMessage,
|
||||||
|
snLocalChannelMember,
|
||||||
|
snLocalKeyPair,
|
||||||
|
snLocalAccount,
|
||||||
|
snLocalAttachment,
|
||||||
|
snLocalSticker,
|
||||||
|
snLocalStickerPack,
|
||||||
|
snLocalRealm,
|
||||||
|
idxChannelAlias,
|
||||||
|
idxChatChannel,
|
||||||
|
idxAccountName,
|
||||||
|
idxAttachmentRid,
|
||||||
|
idxAttachmentAccount,
|
||||||
|
idxRealmAlias,
|
||||||
|
idxRealmAccount,
|
||||||
|
];
|
||||||
|
late final Shape0 snLocalChatChannel = Shape0(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_chat_channel',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape3 snLocalChatMessage = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_chat_message',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_4,
|
||||||
|
_column_10,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 snLocalChannelMember = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_channel_member',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_4,
|
||||||
|
_column_6,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
_column_11,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape2 snLocalKeyPair = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_key_pair',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_5,
|
||||||
|
_column_6,
|
||||||
|
_column_7,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 snLocalAccount = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_account',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_12,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
_column_11,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 snLocalAttachment = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_attachment',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_2,
|
||||||
|
_column_6,
|
||||||
|
_column_3,
|
||||||
|
_column_11,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape7 snLocalSticker = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_sticker',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_15,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape8 snLocalStickerPack = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_sticker_pack',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape9 snLocalRealm = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_realm',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_16,
|
||||||
|
_column_2,
|
||||||
|
_column_6,
|
||||||
|
_column_3,
|
||||||
|
_column_11,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
final i1.Index idxChannelAlias = i1.Index('idx_channel_alias',
|
||||||
|
'CREATE INDEX idx_channel_alias ON sn_local_chat_channel (alias)');
|
||||||
|
final i1.Index idxChatChannel = i1.Index('idx_chat_channel',
|
||||||
|
'CREATE INDEX idx_chat_channel ON sn_local_chat_message (channel_id)');
|
||||||
|
final i1.Index idxAccountName = i1.Index('idx_account_name',
|
||||||
|
'CREATE INDEX idx_account_name ON sn_local_account (name)');
|
||||||
|
final i1.Index idxAttachmentRid = i1.Index('idx_attachment_rid',
|
||||||
|
'CREATE INDEX idx_attachment_rid ON sn_local_attachment (rid)');
|
||||||
|
final i1.Index idxAttachmentAccount = i1.Index('idx_attachment_account',
|
||||||
|
'CREATE INDEX idx_attachment_account ON sn_local_attachment (account_id)');
|
||||||
|
final i1.Index idxRealmAlias = i1.Index('idx_realm_alias',
|
||||||
|
'CREATE INDEX idx_realm_alias ON sn_local_realm (alias)');
|
||||||
|
final i1.Index idxRealmAccount = i1.Index('idx_realm_account',
|
||||||
|
'CREATE INDEX idx_realm_account ON sn_local_realm (account_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape9 extends i0.VersionedTable {
|
||||||
|
Shape9({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get alias =>
|
||||||
|
columnsByName['alias']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get content =>
|
||||||
|
columnsByName['content']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get accountId =>
|
||||||
|
columnsByName['account_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get cacheExpiredAt =>
|
||||||
|
columnsByName['cache_expired_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_16(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('alias', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways('UNIQUE'));
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@ -428,6 +633,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from2To3(migrator, schema);
|
await from2To3(migrator, schema);
|
||||||
return 3;
|
return 3;
|
||||||
|
case 3:
|
||||||
|
final schema = Schema4(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from3To4(migrator, schema);
|
||||||
|
return 4;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@ -437,9 +647,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
i1.OnUpgrade stepByStep({
|
i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
}) =>
|
}) =>
|
||||||
i0.VersionedSchema.stepByStepHelper(
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
from2To3: from2To3,
|
from2To3: from2To3,
|
||||||
|
from3To4: from3To4,
|
||||||
));
|
));
|
||||||
|
45
lib/database/realm.dart
Normal file
45
lib/database/realm.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
|
class SnRealmConverter extends TypeConverter<SnRealm, String>
|
||||||
|
with JsonTypeConverter2<SnRealm, String, Map<String, Object?>> {
|
||||||
|
const SnRealmConverter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnRealm fromSql(String fromDb) {
|
||||||
|
return fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toSql(SnRealm value) {
|
||||||
|
return jsonEncode(toJson(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnRealm fromJson(Map<String, Object?> json) {
|
||||||
|
return SnRealm.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object?> toJson(SnRealm value) {
|
||||||
|
return value.toJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TableIndex(name: 'idx_realm_alias', columns: {#alias})
|
||||||
|
@TableIndex(name: 'idx_realm_account', columns: {#accountId})
|
||||||
|
class SnLocalRealm extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
|
||||||
|
TextColumn get alias => text().unique()();
|
||||||
|
|
||||||
|
TextColumn get content => text().map(const SnRealmConverter())();
|
||||||
|
|
||||||
|
IntColumn get accountId => integer()();
|
||||||
|
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
DateTimeColumn get cacheExpiredAt => dateTime()();
|
||||||
|
}
|
114
lib/main.dart
114
lib/main.dart
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math' hide log;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
@ -12,6 +13,7 @@ import 'package:firebase_core/firebase_core.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:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.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';
|
||||||
@ -19,6 +21,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/firebase_options.dart';
|
import 'package:surface/firebase_options.dart';
|
||||||
import 'package:surface/logger.dart';
|
import 'package:surface/logger.dart';
|
||||||
import 'package:surface/providers/channel.dart';
|
import 'package:surface/providers/channel.dart';
|
||||||
@ -46,6 +49,7 @@ import 'package:surface/router.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:surface/widgets/menu_bar.dart';
|
import 'package:surface/widgets/menu_bar.dart';
|
||||||
|
import 'package:surface/widgets/version_label.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
@ -228,6 +232,9 @@ class _AppSplashScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
||||||
|
bool _isBusy = false;
|
||||||
|
String _phaseText = 'appInitStarting';
|
||||||
|
|
||||||
void _tryRequestRating() async {
|
void _tryRequestRating() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.containsKey('first_boot_time')) {
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
@ -287,6 +294,11 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setPhaseText(String text) {
|
||||||
|
_phaseText = 'appInit${text.capitalize()}'.tr();
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
Future<void> _initialize() async {
|
||||||
try {
|
try {
|
||||||
final cfg = context.read<ConfigProvider>();
|
final cfg = context.read<ConfigProvider>();
|
||||||
@ -299,31 +311,45 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
// The Network initialization must be done after the HomeWidget initialization
|
// The Network initialization must be done after the HomeWidget initialization
|
||||||
// The Network initialization will save the server url to the HomeWidget
|
// The Network initialization will save the server url to the HomeWidget
|
||||||
// The Network initialization will also save initialize the Config, so it not need to be initialized again
|
// The Network initialization will also save initialize the Config, so it not need to be initialized again
|
||||||
|
_setPhaseText('network');
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.initializeUserAgent();
|
await sn.initializeUserAgent();
|
||||||
await sn.setConfigWithNative();
|
await sn.setConfigWithNative();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('userdata');
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
await ua.initialize();
|
await ua.initialize();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('websocket');
|
||||||
final ws = context.read<WebSocketProvider>();
|
final ws = context.read<WebSocketProvider>();
|
||||||
await ws.tryConnect();
|
await ws.tryConnect();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('notification');
|
||||||
final notify = context.read<NotificationProvider>();
|
final notify = context.read<NotificationProvider>();
|
||||||
notify.listen();
|
notify.listen();
|
||||||
await notify.registerPushNotifications();
|
await notify.registerPushNotifications();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('keyPair');
|
||||||
final kp = context.read<KeyPairProvider>();
|
final kp = context.read<KeyPairProvider>();
|
||||||
await kp.reloadActive();
|
await kp.reloadActive();
|
||||||
kp.listen();
|
kp.listen();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('stickers');
|
||||||
final sticker = context.read<SnStickerProvider>();
|
final sticker = context.read<SnStickerProvider>();
|
||||||
await sticker.listSticker();
|
await sticker.listSticker();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_setPhaseText('userDirectory');
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
final userCacheSize = await ud.loadAccountCache();
|
await ud.loadAccountCache();
|
||||||
logging.info('[Users] Loaded local user cache, size: $userCacheSize');
|
if (!mounted) return;
|
||||||
logging.info('[Bootstrap] Everything initialized!');
|
_setPhaseText('realm');
|
||||||
|
final rm = context.read<SnRealmProvider>();
|
||||||
|
await rm.refreshAvailableRealms();
|
||||||
|
if (!mounted) return;
|
||||||
|
_setPhaseText('chat');
|
||||||
|
final ct = context.read<ChatChannelProvider>();
|
||||||
|
await ct.refreshAvailableChannels();
|
||||||
|
_setPhaseText('done');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await context.showErrorDialog(err);
|
await context.showErrorDialog(err);
|
||||||
@ -399,6 +425,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
_isBusy = true;
|
||||||
if (!kIsWeb && !(Platform.isIOS || Platform.isAndroid)) {
|
if (!kIsWeb && !(Platform.isIOS || Platform.isAndroid)) {
|
||||||
_appLifecycleListener = AppLifecycleListener(
|
_appLifecycleListener = AppLifecycleListener(
|
||||||
onExitRequested: _onExitRequested,
|
onExitRequested: _onExitRequested,
|
||||||
@ -412,6 +439,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
_postInitialization();
|
_postInitialization();
|
||||||
_tryRequestRating();
|
_tryRequestRating();
|
||||||
_checkForUpdate();
|
_checkForUpdate();
|
||||||
|
setState(() => _isBusy = false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,7 +529,44 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return SizeChangedLayoutNotifier(
|
return SizeChangedLayoutNotifier(
|
||||||
child: widget.child,
|
child: _isBusy
|
||||||
|
? Material(
|
||||||
|
key: Key('app-splash-screen-$_isBusy'),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
CustomPaint(painter: GraphPainter()),
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 240,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/icon/icon.png',
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
Text('Solar Network').bold(),
|
||||||
|
AppVersionLabel(),
|
||||||
|
Gap(8),
|
||||||
|
Text(
|
||||||
|
_phaseText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Gap(16),
|
||||||
|
const LinearProgressIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: widget.child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -509,3 +574,44 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GraphPainter extends CustomPainter {
|
||||||
|
final Random random = Random();
|
||||||
|
final int numNodes = 20;
|
||||||
|
final double maxDistance = 100; // Max distance to draw a line
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paintNode = Paint()..color = Colors.white;
|
||||||
|
final paintEdge = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.3)
|
||||||
|
..strokeWidth = 1;
|
||||||
|
|
||||||
|
// Generate random points
|
||||||
|
List<Offset> nodes = List.generate(
|
||||||
|
numNodes,
|
||||||
|
(_) => Offset(
|
||||||
|
random.nextDouble() * size.width,
|
||||||
|
random.nextDouble() * size.height,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw edges between close nodes
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
for (var j = i + 1; j < nodes.length; j++) {
|
||||||
|
double distance = (nodes[i] - nodes[j]).distance;
|
||||||
|
if (distance < maxDistance) {
|
||||||
|
canvas.drawLine(nodes[i], nodes[j], paintEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw nodes
|
||||||
|
for (var node in nodes) {
|
||||||
|
canvas.drawCircle(node, 4, paintNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
@ -28,6 +28,19 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
_rels = context.read<SnRealmProvider>();
|
_rels = context.read<SnRealmProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<SnChannel> _availableChannels = List.empty(growable: true);
|
||||||
|
|
||||||
|
List<SnChannel> get availableChannels => _availableChannels;
|
||||||
|
|
||||||
|
Future<void> refreshAvailableChannels() async {
|
||||||
|
final stream = fetchChannels();
|
||||||
|
stream.listen((ele) {
|
||||||
|
_availableChannels.clear();
|
||||||
|
_availableChannels.addAll(ele);
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||||
await Future.wait(
|
await Future.wait(
|
||||||
channels.map(
|
channels.map(
|
||||||
|
@ -21,6 +21,7 @@ const kAppRealmCompactView = 'app_realm_compact_view';
|
|||||||
const kAppCustomFonts = 'app_custom_fonts';
|
const kAppCustomFonts = 'app_custom_fonts';
|
||||||
const kAppMixedFeed = 'app_mixed_feed';
|
const kAppMixedFeed = 'app_mixed_feed';
|
||||||
const kAppAutoTranslate = 'app_auto_translate';
|
const kAppAutoTranslate = 'app_auto_translate';
|
||||||
|
const kAppHideBottomNav = 'app_hide_bottom_nav';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
@ -91,6 +92,15 @@ class ConfigProvider extends ChangeNotifier {
|
|||||||
return prefs.getBool(kAppAutoTranslate) ?? false;
|
return prefs.getBool(kAppAutoTranslate) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get hideBottomNav {
|
||||||
|
return prefs.getBool(kAppHideBottomNav) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hideBottomNav(bool value) {
|
||||||
|
prefs.setBool(kAppHideBottomNav, value);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
set autoTranslate(bool value) {
|
set autoTranslate(bool value) {
|
||||||
prefs.setBool(kAppAutoTranslate, value);
|
prefs.setBool(kAppAutoTranslate, value);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.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:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
class AppNavDestination {
|
class AppNavDestination {
|
||||||
final String label;
|
final String label;
|
||||||
@ -24,13 +25,10 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
int? get currentIndex => _currentIndex;
|
int? get currentIndex => _currentIndex;
|
||||||
|
|
||||||
static const List<String> kShowBottomNavScreen = [
|
List<String> get showBottomNavScreen => destinations
|
||||||
'home',
|
.where((ele) => ele.isPinned)
|
||||||
'explore',
|
.map((ele) => ele.screen)
|
||||||
'account',
|
.toList();
|
||||||
'album',
|
|
||||||
'chat',
|
|
||||||
];
|
|
||||||
|
|
||||||
static const List<AppNavDestination> kAllDestination = [
|
static const List<AppNavDestination> kAllDestination = [
|
||||||
AppNavDestination(
|
AppNavDestination(
|
||||||
@ -88,7 +86,7 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
'home',
|
'home',
|
||||||
'explore',
|
'explore',
|
||||||
'chat',
|
'chat',
|
||||||
'account',
|
'realm',
|
||||||
];
|
];
|
||||||
|
|
||||||
List<AppNavDestination> destinations = [];
|
List<AppNavDestination> destinations = [];
|
||||||
@ -143,4 +141,11 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
_currentIndex = idx;
|
_currentIndex = idx;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SnRealm? focusedRealm;
|
||||||
|
|
||||||
|
void setFocusedRealm(SnRealm? realm) {
|
||||||
|
focusedRealm = realm;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,13 +321,13 @@ class SnAttachmentProvider {
|
|||||||
uuid: ele.uuid,
|
uuid: ele.uuid,
|
||||||
content: ele,
|
content: ele,
|
||||||
accountId: ele.accountId,
|
accountId: ele.accountId,
|
||||||
cacheExpiredAt: DateTime.now().add(const Duration(days: 7)),
|
cacheExpiredAt: DateTime.now().add(const Duration(hours: 1)),
|
||||||
),
|
),
|
||||||
onConflict: DoUpdate(
|
onConflict: DoUpdate(
|
||||||
(_) => SnLocalAttachmentCompanion.custom(
|
(_) => SnLocalAttachmentCompanion.custom(
|
||||||
content: Constant(jsonEncode(ele.toJson())),
|
content: Constant(jsonEncode(ele.toJson())),
|
||||||
cacheExpiredAt:
|
cacheExpiredAt:
|
||||||
Constant(DateTime.now().add(const Duration(days: 7))),
|
Constant(DateTime.now().add(const Duration(hours: 1))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
class SnRealmProvider {
|
class SnRealmProvider {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
|
late final DatabaseProvider _dt;
|
||||||
|
|
||||||
SnRealmProvider(BuildContext context) {
|
SnRealmProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
_dt = context.read<DatabaseProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, SnRealm> _cache = {};
|
final Map<String, SnRealm> _cache = {};
|
||||||
|
List<SnRealm> _availableRealms = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> refreshAvailableRealms() async {
|
||||||
|
_availableRealms = await listAvailableRealms();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SnRealm> get availableRealms => _availableRealms;
|
||||||
|
|
||||||
Future<List<SnRealm>> listAvailableRealms() async {
|
Future<List<SnRealm>> listAvailableRealms() async {
|
||||||
final resp = await _sn.client.get('/cgi/id/realms/me/available');
|
final resp = await _sn.client.get('/cgi/id/realms/me/available');
|
||||||
@ -21,6 +35,7 @@ class SnRealmProvider {
|
|||||||
_cache[realm.alias] = realm;
|
_cache[realm.alias] = realm;
|
||||||
_cache[realm.id.toString()] = realm;
|
_cache[realm.id.toString()] = realm;
|
||||||
}
|
}
|
||||||
|
_saveToLocal(out);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +43,43 @@ class SnRealmProvider {
|
|||||||
if (_cache.containsKey(aliasOrId.toString())) {
|
if (_cache.containsKey(aliasOrId.toString())) {
|
||||||
return _cache[aliasOrId.toString()]!;
|
return _cache[aliasOrId.toString()]!;
|
||||||
}
|
}
|
||||||
|
final localResp = await (_dt.db.snLocalRealm.select()
|
||||||
|
..where((e) =>
|
||||||
|
e.id.equals(aliasOrId is int ? aliasOrId : 0) |
|
||||||
|
e.alias.equals(aliasOrId.toString()))
|
||||||
|
..where((e) => e.cacheExpiredAt.isBiggerThanValue(DateTime.now())))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (localResp != null) {
|
||||||
|
_cache[localResp.content.id.toString()] = localResp.content;
|
||||||
|
_cache[localResp.content.alias] = localResp.content;
|
||||||
|
return localResp.content;
|
||||||
|
}
|
||||||
final resp = await _sn.client.get('/cgi/id/realms/$aliasOrId');
|
final resp = await _sn.client.get('/cgi/id/realms/$aliasOrId');
|
||||||
final out = SnRealm.fromJson(resp.data);
|
final out = SnRealm.fromJson(resp.data);
|
||||||
_cache[out.alias] = out;
|
_cache[out.alias] = out;
|
||||||
_cache[out.id.toString()] = out;
|
_cache[out.id.toString()] = out;
|
||||||
|
_saveToLocal([out]);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _saveToLocal(Iterable<SnRealm> out) async {
|
||||||
|
for (final ele in out) {
|
||||||
|
await _dt.db.snLocalRealm.insertOne(
|
||||||
|
SnLocalRealmCompanion.insert(
|
||||||
|
id: Value(ele.id),
|
||||||
|
alias: ele.alias,
|
||||||
|
content: ele,
|
||||||
|
accountId: ele.accountId,
|
||||||
|
cacheExpiredAt: DateTime.now().add(const Duration(hours: 1)),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate(
|
||||||
|
(_) => SnLocalRealmCompanion.custom(
|
||||||
|
content: Constant(jsonEncode(ele.toJson())),
|
||||||
|
cacheExpiredAt:
|
||||||
|
Constant(DateTime.now().add(const Duration(hours: 1))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import 'package:surface/screens/account/badges.dart';
|
|||||||
import 'package:surface/screens/account/contact_methods.dart';
|
import 'package:surface/screens/account/contact_methods.dart';
|
||||||
import 'package:surface/screens/account/factor_settings.dart';
|
import 'package:surface/screens/account/factor_settings.dart';
|
||||||
import 'package:surface/screens/account/keypairs.dart';
|
import 'package:surface/screens/account/keypairs.dart';
|
||||||
|
import 'package:surface/screens/account/prefs/notify.dart';
|
||||||
|
import 'package:surface/screens/account/prefs/security.dart';
|
||||||
import 'package:surface/screens/account/profile_page.dart';
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/screens/account/profile_edit.dart';
|
import 'package:surface/screens/account/profile_edit.dart';
|
||||||
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
||||||
@ -37,6 +39,7 @@ import 'package:surface/screens/post/post_shuffle.dart';
|
|||||||
import 'package:surface/screens/post/publisher_page.dart';
|
import 'package:surface/screens/post/publisher_page.dart';
|
||||||
import 'package:surface/screens/post/post_search.dart';
|
import 'package:surface/screens/post/post_search.dart';
|
||||||
import 'package:surface/screens/realm.dart';
|
import 'package:surface/screens/realm.dart';
|
||||||
|
import 'package:surface/screens/realm/community.dart';
|
||||||
import 'package:surface/screens/realm/manage.dart';
|
import 'package:surface/screens/realm/manage.dart';
|
||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
import 'package:surface/screens/realm/realm_discovery.dart';
|
import 'package:surface/screens/realm/realm_discovery.dart';
|
||||||
@ -161,6 +164,18 @@ final _appRoutes = [
|
|||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'accountSettings',
|
name: 'accountSettings',
|
||||||
builder: (context, state) => AccountSettingsScreen(),
|
builder: (context, state) => AccountSettingsScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/notify',
|
||||||
|
name: 'accountSettingsNotify',
|
||||||
|
builder: (context, state) => const AccountNotifyPrefsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/auth',
|
||||||
|
name: 'accountSettingsSecurity',
|
||||||
|
builder: (context, state) => const AccountSecurityPrefsScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings/factors',
|
path: '/settings/factors',
|
||||||
@ -245,6 +260,13 @@ final _appRoutes = [
|
|||||||
child: const RealmScreen(),
|
child: const RealmScreen(),
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/:alias/community',
|
||||||
|
name: 'realmCommunity',
|
||||||
|
builder: (context, state) => RealmCommunityScreen(
|
||||||
|
alias: state.pathParameters['alias']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/manage',
|
path: '/manage',
|
||||||
name: 'realmManage',
|
name: 'realmManage',
|
||||||
|
@ -97,6 +97,26 @@ class AccountSettingsScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('accountContactMethods');
|
GoRouter.of(context).pushNamed('accountContactMethods');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountSettingsNotify').tr(),
|
||||||
|
subtitle: Text('accountSettingsNotifyDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.notifications),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountSettingsNotify');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountSettingsSecurity').tr(),
|
||||||
|
subtitle: Text('accountSettingsSecurityDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.shield),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountSettingsSecurity');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountProfileEdit').tr(),
|
title: Text('accountProfileEdit').tr(),
|
||||||
subtitle: Text('accountProfileEditSubtitle').tr(),
|
subtitle: Text('accountProfileEditSubtitle').tr(),
|
||||||
|
@ -1,11 +1,122 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class AccountNotifyPrefsScreen extends StatelessWidget {
|
final Map<String, String> kNotifyTopicMap = {
|
||||||
|
'interactive.reply': 'notificationTopicPostReply'.tr(),
|
||||||
|
'interactive.feedback': 'notificationTopicPostFeedback'.tr(),
|
||||||
|
'interactive.subscription': 'notificationTopicPostSubscription'.tr(),
|
||||||
|
'messaging.message': 'notificationTopicMessaging'.tr(),
|
||||||
|
'messaging.call': 'notificationTopicMessagingCall'.tr(),
|
||||||
|
'general': 'notificationTopicGeneral'.tr(),
|
||||||
|
};
|
||||||
|
|
||||||
|
class AccountNotifyPrefsScreen extends StatefulWidget {
|
||||||
const AccountNotifyPrefsScreen({super.key});
|
const AccountNotifyPrefsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountNotifyPrefsScreen> createState() =>
|
||||||
|
_AccountNotifyPrefsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountNotifyPrefsScreenState extends State<AccountNotifyPrefsScreen> {
|
||||||
|
bool _isBusy = true;
|
||||||
|
|
||||||
|
Map<String, bool> _config = {};
|
||||||
|
|
||||||
|
Future<void> _getPreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final resp = await sn.client.get('/cgi/id/preferences/notifications');
|
||||||
|
_config = resp.data['config']
|
||||||
|
.map((k, v) => MapEntry(k, v as bool))
|
||||||
|
.cast<String, bool>();
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _savePreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sn.client.put(
|
||||||
|
'/cgi/id/preferences/notifications',
|
||||||
|
data: {
|
||||||
|
'config': _config,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('accountSettingsApplied'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold();
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('accountSettingsNotify').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.save),
|
||||||
|
title: Text('save').tr(),
|
||||||
|
enabled: !_isBusy,
|
||||||
|
onTap: () {
|
||||||
|
_savePreferences();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: kNotifyTopicMap.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final element = kNotifyTopicMap.entries.elementAt(index);
|
||||||
|
return CheckboxListTile(
|
||||||
|
title: Text(element.value),
|
||||||
|
subtitle: Text(
|
||||||
|
element.key,
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
value: _config[element.key] ?? true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_config[element.key] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
lib/screens/account/prefs/security.dart
Normal file
147
lib/screens/account/prefs/security.dart
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class AccountSecurityPrefsScreen extends StatefulWidget {
|
||||||
|
const AccountSecurityPrefsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountSecurityPrefsScreen> createState() =>
|
||||||
|
_AccountSecurityPrefsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountSecurityPrefsScreenState
|
||||||
|
extends State<AccountSecurityPrefsScreen> {
|
||||||
|
bool _isBusy = true;
|
||||||
|
|
||||||
|
Map<String, dynamic> _config = {
|
||||||
|
'maximum_auth_steps': 2,
|
||||||
|
'always_risky': false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> _getPreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final resp = await sn.client.get('/cgi/id/preferences/auth');
|
||||||
|
_config = resp.data['config']
|
||||||
|
.map((k, v) => MapEntry(k, v as bool))
|
||||||
|
.cast<String, bool>();
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _savePreferences() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sn.client.put(
|
||||||
|
'/cgi/id/preferences/auth',
|
||||||
|
data: {
|
||||||
|
'config': _config,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('accountSettingsApplied'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
title: Text('accountSettingsSecurity').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Icons.save),
|
||||||
|
title: Text('save').tr(),
|
||||||
|
enabled: !_isBusy,
|
||||||
|
onTap: () {
|
||||||
|
_savePreferences();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('authMaximumAuthSteps').tr(),
|
||||||
|
subtitle: Text('authMaximumAuthStepsDescription')
|
||||||
|
.plural(_config['maximum_auth_steps'] ?? 2),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -4,
|
||||||
|
),
|
||||||
|
icon: const Icon(Symbols.remove),
|
||||||
|
onPressed: () {
|
||||||
|
if (_config['maximum_auth_steps'] > 1) {
|
||||||
|
setState(() => _config['maximum_auth_steps']--);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -4,
|
||||||
|
),
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
onPressed: () {
|
||||||
|
if (_config['maximum_auth_steps'] < 99) {
|
||||||
|
setState(() => _config['maximum_auth_steps']++);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: Text('authAlwaysRisky').tr(),
|
||||||
|
subtitle: Text('authAlwaysRiskyDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
value: _config['always_risky'] ?? false,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _config['always_risky'] = value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ 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/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/screens/captcha.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';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@ -33,10 +34,20 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
final username = _usernameController.value.text;
|
final username = _usernameController.value.text;
|
||||||
final nickname = _nicknameController.value.text;
|
final nickname = _nicknameController.value.text;
|
||||||
final password = _passwordController.value.text;
|
final password = _passwordController.value.text;
|
||||||
if (email.isEmpty || username.isEmpty || nickname.isEmpty || password.isEmpty) {
|
if (email.isEmpty ||
|
||||||
|
username.isEmpty ||
|
||||||
|
nickname.isEmpty ||
|
||||||
|
password.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TurnstileScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (captchaTk == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.post('/cgi/id/users', data: {
|
await sn.client.post('/cgi/id/users', data: {
|
||||||
@ -45,6 +56,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password,
|
||||||
'language': EasyLocalization.of(context)!.currentLocale.toString(),
|
'language': EasyLocalization.of(context)!.currentLocale.toString(),
|
||||||
|
'captcha_token': captchaTk,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@ -91,8 +103,11 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.length < 4 || value.length > 32) {
|
if (value == null ||
|
||||||
return 'fieldUsernameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
|
value.length < 4 ||
|
||||||
|
value.length > 32) {
|
||||||
|
return 'fieldUsernameLengthLimit'
|
||||||
|
.tr(args: [4.toString(), 32.toString()]);
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
|
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
|
||||||
return 'fieldUsernameAlphanumOnly'.tr();
|
return 'fieldUsernameAlphanumOnly'.tr();
|
||||||
@ -108,13 +123,17 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldUsername'.tr(),
|
labelText: 'fieldUsername'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.length < 4 || value.length > 32) {
|
if (value == null ||
|
||||||
return 'fieldNicknameLengthLimit'.tr(args: [4.toString(), 32.toString()]);
|
value.length < 4 ||
|
||||||
|
value.length > 32) {
|
||||||
|
return 'fieldNicknameLengthLimit'
|
||||||
|
.tr(args: [4.toString(), 32.toString()]);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -127,7 +146,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldNickname'.tr(),
|
labelText: 'fieldNickname'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@ -149,7 +169,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldEmail'.tr(),
|
labelText: 'fieldEmail'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@ -169,7 +190,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldPassword'.tr(),
|
labelText: 'fieldPassword'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 7),
|
).padding(horizontal: 7),
|
||||||
@ -186,9 +208,13 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
Text(
|
Text(
|
||||||
'termAcceptNextWithAgree'.tr(),
|
'termAcceptNextWithAgree'.tr(),
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style:
|
||||||
color: Theme.of(context).colorScheme.onSurface.withAlpha((255 * 0.75).round()),
|
Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface
|
||||||
|
.withAlpha((255 * 0.75).round()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
38
lib/screens/captcha.dart
Normal file
38
lib/screens/captcha.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class TurnstileScreen extends StatefulWidget {
|
||||||
|
const TurnstileScreen({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TurnstileScreen> createState() => _TurnstileScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TurnstileScreenState extends State<TurnstileScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text("reCaptcha").tr()),
|
||||||
|
body: InAppWebView(
|
||||||
|
initialUrlRequest: URLRequest(
|
||||||
|
url: WebUri('${cfg.serverUrl}/captcha?redirect_uri=solink://captcha'),
|
||||||
|
),
|
||||||
|
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||||
|
Uri? url = navigationAction.request.url;
|
||||||
|
if (url != null && url.queryParameters.containsKey('captcha_tk')) {
|
||||||
|
Navigator.pop(context, url.queryParameters['captcha_tk']!);
|
||||||
|
return NavigationActionPolicy.CANCEL;
|
||||||
|
}
|
||||||
|
return NavigationActionPolicy.ALLOW;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -52,8 +52,10 @@ class ChatRoomScreen extends StatefulWidget {
|
|||||||
class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
bool _isCalling = false;
|
bool _isCalling = false;
|
||||||
|
bool _isJoining = false;
|
||||||
|
|
||||||
SnChannel? _channel;
|
SnChannel? _channel;
|
||||||
|
SnChannelMember? _currentMember;
|
||||||
SnChannelMember? _otherMember;
|
SnChannelMember? _otherMember;
|
||||||
SnChatCall? _ongoingCall;
|
SnChatCall? _ongoingCall;
|
||||||
|
|
||||||
@ -67,7 +69,24 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
|
|
||||||
StreamSubscription? _wsSubscription;
|
StreamSubscription? _wsSubscription;
|
||||||
|
|
||||||
// TODO fetch user identity and ask them to join the channel or not
|
Future<void> _joinChannel() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isJoining = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
await sn.client
|
||||||
|
.post('/cgi/im/channels/${_channel!.keyPath}/members', data: {
|
||||||
|
'related': ua.user?.name,
|
||||||
|
});
|
||||||
|
_initializeChat();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isJoining = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchChannel() async {
|
Future<void> _fetchChannel() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -76,6 +95,12 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
_channel = await chan.getChannel('${widget.scope}:${widget.alias}');
|
_channel = await chan.getChannel('${widget.scope}:${widget.alias}');
|
||||||
|
|
||||||
if (!mounted || _channel == null) return;
|
if (!mounted || _channel == null) return;
|
||||||
|
final ct = context.read<ChatChannelProvider>();
|
||||||
|
try {
|
||||||
|
_currentMember = await ct.getChannelProfile(_channel!);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (!mounted || _currentMember == null) return;
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
if (_channel!.type == 1) {
|
if (_channel!.type == 1) {
|
||||||
@ -204,11 +229,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
return a.createdAt.difference(b.createdAt).inMinutes <= 3;
|
return a.createdAt.difference(b.createdAt).inMinutes <= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> _initializeChat() async {
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_messageController = ChatMessageController(context);
|
|
||||||
_fetchChannel().then((_) async {
|
_fetchChannel().then((_) async {
|
||||||
|
if (_currentMember == null) return;
|
||||||
await _messageController.initialize(_channel!);
|
await _messageController.initialize(_channel!);
|
||||||
|
|
||||||
if (widget.extra != null) {
|
if (widget.extra != null) {
|
||||||
@ -230,6 +253,13 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
_fetchOngoingCall(),
|
_fetchOngoingCall(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_messageController = ChatMessageController(context);
|
||||||
|
_initializeChat();
|
||||||
|
|
||||||
_wsSubscription = _ws.pk.stream.listen((event) {
|
_wsSubscription = _ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
@ -281,25 +311,27 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
: _channel?.name ?? 'loading'.tr(),
|
: _channel?.name ?? 'loading'.tr(),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
if (_currentMember != null)
|
||||||
onPressed: () {
|
IconButton(
|
||||||
setState(() => _isEncrypted = !_isEncrypted);
|
onPressed: () {
|
||||||
_inputGlobalKey.currentState?.setEncrypted(_isEncrypted);
|
setState(() => _isEncrypted = !_isEncrypted);
|
||||||
},
|
_inputGlobalKey.currentState?.setEncrypted(_isEncrypted);
|
||||||
icon: _isEncrypted
|
},
|
||||||
? const Icon(Symbols.lock)
|
icon: _isEncrypted
|
||||||
: const Icon(Symbols.no_encryption),
|
? const Icon(Symbols.lock)
|
||||||
),
|
: const Icon(Symbols.no_encryption),
|
||||||
IconButton(
|
),
|
||||||
icon: _ongoingCall == null
|
if (_currentMember != null)
|
||||||
? const Icon(Symbols.call)
|
IconButton(
|
||||||
: const Icon(Symbols.call_end),
|
icon: _ongoingCall == null
|
||||||
onPressed: _isCalling
|
? const Icon(Symbols.call)
|
||||||
? null
|
: const Icon(Symbols.call_end),
|
||||||
: _ongoingCall == null
|
onPressed: _isCalling
|
||||||
? _makeCall
|
? null
|
||||||
: _endCall,
|
: _ongoingCall == null
|
||||||
),
|
? _makeCall
|
||||||
|
: _endCall,
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.more_vert),
|
icon: const Icon(Symbols.more_vert),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -348,7 +380,41 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
).height(_ongoingCall != null ? 54 : 0, animate: true).animate(
|
).height(_ongoingCall != null ? 54 : 0, animate: true).animate(
|
||||||
const Duration(milliseconds: 300),
|
const Duration(milliseconds: 300),
|
||||||
Curves.fastLinearToSlowEaseIn),
|
Curves.fastLinearToSlowEaseIn),
|
||||||
if (_messageController.isPending)
|
if (_currentMember == null && !_isBusy)
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 280),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.person_remove, size: 40, fill: 1),
|
||||||
|
const Gap(8),
|
||||||
|
Text('chatUnjoined'.tr(), textAlign: TextAlign.center)
|
||||||
|
.fontSize(16)
|
||||||
|
.bold(),
|
||||||
|
Text('chatUnjoinedDescription'.tr(),
|
||||||
|
textAlign: TextAlign.center)
|
||||||
|
.fontSize(13),
|
||||||
|
if (_channel!.isPublic)
|
||||||
|
Text('chatUnjoinedPublicDescription'.tr(),
|
||||||
|
textAlign: TextAlign.center)
|
||||||
|
.fontSize(13)
|
||||||
|
.padding(top: 8),
|
||||||
|
if (_channel!.isPublic)
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
onPressed: _isJoining ? null : _joinChannel,
|
||||||
|
child: Text('chatJoin').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (_messageController.isPending)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: const CircularProgressIndicator().center(),
|
child: const CircularProgressIndicator().center(),
|
||||||
)
|
)
|
||||||
@ -403,7 +469,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!_messageController.isPending)
|
if (!_messageController.isPending && _currentMember != null)
|
||||||
Material(
|
Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -449,7 +449,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
|
|||||||
data: ele.toJson(),
|
data: ele.toJson(),
|
||||||
createdAt: ele.createdAt)),
|
createdAt: ele.createdAt)),
|
||||||
);
|
);
|
||||||
_hasLoadedAll = postCount >= _feed.length;
|
_hasLoadedAll = _feed.length >= postCount;
|
||||||
|
|
||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/special_day.dart';
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/widget.dart';
|
import 'package:surface/providers/widget.dart';
|
||||||
|
import 'package:surface/screens/captcha.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
@ -508,11 +509,20 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _doCheckIn() async {
|
Future<void> _doCheckIn() async {
|
||||||
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TurnstileScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (captchaTk == null) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final home = context.read<HomeWidgetProvider>();
|
final home = context.read<HomeWidgetProvider>();
|
||||||
final resp = await sn.client.post('/cgi/id/check-in');
|
final resp = await sn.client.post('/cgi/id/check-in', data: {
|
||||||
|
'captcha_token': captchaTk,
|
||||||
|
});
|
||||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||||
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -45,12 +45,14 @@ class PostEditorExtra {
|
|||||||
final String? title;
|
final String? title;
|
||||||
final String? description;
|
final String? description;
|
||||||
final List<PostWriteMedia>? attachments;
|
final List<PostWriteMedia>? attachments;
|
||||||
|
final SnRealm? realm;
|
||||||
|
|
||||||
const PostEditorExtra({
|
const PostEditorExtra({
|
||||||
this.text,
|
this.text,
|
||||||
this.title,
|
this.title,
|
||||||
this.description,
|
this.description,
|
||||||
this.attachments,
|
this.attachments,
|
||||||
|
this.realm,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +265,7 @@ class _PostEditorScreenState extends State<PostEditorScreen>
|
|||||||
_writeController.descriptionController.text =
|
_writeController.descriptionController.text =
|
||||||
widget.extraProps!.description ?? '';
|
widget.extraProps!.description ?? '';
|
||||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||||
|
_writeController.setRealm(widget.extraProps!.realm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
149
lib/screens/realm/community.dart
Normal file
149
lib/screens/realm/community.dart
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.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/post.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/screens/post/post_editor.dart';
|
||||||
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
class RealmCommunityScreen extends StatefulWidget {
|
||||||
|
final String alias;
|
||||||
|
const RealmCommunityScreen({super.key, required this.alias});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RealmCommunityScreen> createState() => _RealmCommunityScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmCommunityScreenState extends State<RealmCommunityScreen> {
|
||||||
|
SnRealm? _realm;
|
||||||
|
|
||||||
|
Future<void> _fetchRealm() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms/${widget.alias}');
|
||||||
|
_realm = SnRealm.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isBusy = false;
|
||||||
|
int? _totalCount;
|
||||||
|
final List<SnPost> _posts = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _fetchPosts() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final pt = context.read<SnPostContentProvider>();
|
||||||
|
final out = await pt.listPosts(
|
||||||
|
take: 10,
|
||||||
|
offset: _posts.length,
|
||||||
|
realm: _realm?.id.toString(),
|
||||||
|
);
|
||||||
|
_totalCount = out.$2;
|
||||||
|
_posts.addAll(out.$1);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(_realm?.name ?? 'loading'.tr()),
|
||||||
|
),
|
||||||
|
floatingActionButton: _realm != null
|
||||||
|
? FloatingActionButton(
|
||||||
|
child: const Icon(Symbols.edit),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postEditor',
|
||||||
|
extra: PostEditorExtra(realm: _realm!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (_realm == null)
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator().center(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_realm != null)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('realmCommunity'.tr(args: [_realm!.name]))
|
||||||
|
.fontSize(17)
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
|
Text('postTotalCount'.plural(_totalCount ?? 0))
|
||||||
|
.fontSize(13)
|
||||||
|
.opacity(0.8)
|
||||||
|
.padding(horizontal: 20, bottom: 4),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 16),
|
||||||
|
const Divider(height: 1),
|
||||||
|
if (_realm != null)
|
||||||
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchPosts,
|
||||||
|
child: InfiniteList(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
itemCount: _posts.length,
|
||||||
|
isLoading: _isBusy,
|
||||||
|
hasReachedMax:
|
||||||
|
_totalCount != null && _posts.length >= _totalCount!,
|
||||||
|
onFetchData: _fetchPosts,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final post = _posts[idx];
|
||||||
|
return OpenablePostItem(
|
||||||
|
data: post,
|
||||||
|
maxWidth: 640,
|
||||||
|
onChanged: (data) {
|
||||||
|
setState(() => _posts[idx] = data);
|
||||||
|
},
|
||||||
|
onDeleted: () {
|
||||||
|
setState(() => _posts.removeAt(idx));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) =>
|
||||||
|
const Divider().padding(vertical: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -318,7 +318,7 @@ class _RealmPostListWidgetState extends State<_RealmPostListWidget> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
separatorBuilder: (_, __) => const Divider().padding(vertical: 2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).padding(top: 8);
|
).padding(top: 8);
|
||||||
|
@ -336,6 +336,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.hide),
|
||||||
|
title: Text('settingsHideBottomNav').tr(),
|
||||||
|
subtitle: Text('settingsHideBottomNavDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
value: _prefs.getBool(kAppHideBottomNav) ?? false,
|
||||||
|
onChanged: (value) {
|
||||||
|
_prefs.setBool(kAppHideBottomNav, value ?? false);
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
cfg.calcDrawerSize(context);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.font_download),
|
leading: const Icon(Symbols.font_download),
|
||||||
title: Text('settingsCustomFonts').tr(),
|
title: Text('settingsCustomFonts').tr(),
|
||||||
|
@ -88,6 +88,8 @@ Future<ThemeData> createAppTheme(
|
|||||||
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
|
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
progressIndicatorTheme: ProgressIndicatorThemeData(year2023: false),
|
||||||
|
sliderTheme: SliderThemeData(year2023: false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
key: Key('chat-message-${data.id}'),
|
key: Key('chat-message-${data.id}'),
|
||||||
iconOnLeftSwipe: Symbols.reply,
|
iconOnLeftSwipe: Symbols.reply,
|
||||||
iconOnRightSwipe: Symbols.edit,
|
iconOnRightSwipe: Symbols.edit,
|
||||||
swipeSensitivity: 20,
|
swipeSensitivity: 10,
|
||||||
onLeftSwipe: onReply != null ? (_) => onReply!(data) : null,
|
onLeftSwipe: onReply != null ? (_) => onReply!(data) : null,
|
||||||
onRightSwipe: (onEdit != null && isOwner) ? (_) => onEdit!(data) : null,
|
onRightSwipe: (onEdit != null && isOwner) ? (_) => onEdit!(data) : null,
|
||||||
child: ContextMenuArea(
|
child: ContextMenuArea(
|
||||||
|
@ -36,10 +36,12 @@ class ChatTypingIndicator extends StatelessWidget {
|
|||||||
'messageTyping'
|
'messageTyping'
|
||||||
.plural(controller.typingMembers.length, args: [
|
.plural(controller.typingMembers.length, args: [
|
||||||
controller.typingMembers
|
controller.typingMembers
|
||||||
.map((ele) => (ele.nick?.isNotEmpty ?? false)
|
.map(
|
||||||
? ele.nick!
|
(ele) => (ele.nick?.isNotEmpty ?? false)
|
||||||
: ud.getFromCache(ele.accountId)?.name ??
|
? ele.nick!
|
||||||
'unknown')
|
: ud.getFromCache(ele.accountId)?.nick ??
|
||||||
|
'unknown',
|
||||||
|
)
|
||||||
.join(', '),
|
.join(', '),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
@ -76,7 +76,10 @@ class _LoadingIndicatorState extends State<LoadingIndicator>
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
width: 16,
|
width: 16,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2.5),
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.5,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('loading').tr(),
|
Text('loading').tr(),
|
||||||
|
@ -176,7 +176,7 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
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']?.toDouble() ??
|
||||||
switch (attachment.mimetype
|
switch (attachment.mimetype
|
||||||
.split('/')
|
.split('/')
|
||||||
.firstOrNull) {
|
.firstOrNull) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.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';
|
||||||
@ -83,6 +84,16 @@ class AppSystemMenuBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
PlatformMenuItem(
|
||||||
|
shortcut: const SingleActivator(
|
||||||
|
LogicalKeyboardKey.keyH,
|
||||||
|
meta: true,
|
||||||
|
),
|
||||||
|
label: 'trayMenuHide'.tr(),
|
||||||
|
onSelected: () {
|
||||||
|
appWindow.hide();
|
||||||
|
},
|
||||||
|
),
|
||||||
if (onQuit != null)
|
if (onQuit != null)
|
||||||
PlatformMenuItem(
|
PlatformMenuItem(
|
||||||
shortcut: const SingleActivator(
|
shortcut: const SingleActivator(
|
||||||
|
@ -37,17 +37,15 @@ class _AppBottomNavigationBarState extends State<AppBottomNavigationBar> {
|
|||||||
...nav.destinations.where((ele) => ele.isPinned),
|
...nav.destinations.where((ele) => ele.isPinned),
|
||||||
];
|
];
|
||||||
|
|
||||||
return BottomNavigationBar(
|
return NavigationBar(
|
||||||
currentIndex: nav.getIndexInRange(0, nav.pinnedDestinationCount),
|
selectedIndex: nav.getIndexInRange(0, nav.pinnedDestinationCount),
|
||||||
type: BottomNavigationBarType.fixed,
|
destinations: destinations.map((ele) {
|
||||||
showUnselectedLabels: false,
|
return NavigationDestination(
|
||||||
items: destinations.map((ele) {
|
|
||||||
return BottomNavigationBarItem(
|
|
||||||
icon: ele.icon,
|
icon: ele.icon,
|
||||||
label: ele.label.tr(),
|
label: ele.label.tr(),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onTap: (idx) {
|
onDestinationSelected: (idx) {
|
||||||
nav.setIndex(idx);
|
nav.setIndex(idx);
|
||||||
GoRouter.of(context).goNamed(destinations[idx].screen);
|
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.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: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: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/channel.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/navigation.dart';
|
import 'package:surface/providers/navigation.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:surface/widgets/version_label.dart';
|
import 'package:surface/widgets/version_label.dart';
|
||||||
|
|
||||||
class AppNavigationDrawer extends StatefulWidget {
|
class AppNavigationDrawer extends StatefulWidget {
|
||||||
@ -25,12 +34,15 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<NavigationProvider>().autoDetectIndex(GoRouter.maybeOf(context));
|
context
|
||||||
|
.read<NavigationProvider>()
|
||||||
|
.autoDetectIndex(GoRouter.maybeOf(context));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
final nav = context.watch<NavigationProvider>();
|
final nav = context.watch<NavigationProvider>();
|
||||||
final cfg = context.watch<ConfigProvider>();
|
final cfg = context.watch<ConfigProvider>();
|
||||||
|
|
||||||
@ -39,60 +51,258 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: nav,
|
listenable: nav,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final destinations = [
|
return Drawer(
|
||||||
...nav.destinations.where((ele) => ele.isPinned),
|
|
||||||
...nav.destinations.where((ele) => !ele.isPinned),
|
|
||||||
];
|
|
||||||
|
|
||||||
return NavigationDrawer(
|
|
||||||
elevation: widget.elevation,
|
elevation: widget.elevation,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
selectedIndex: nav.currentIndex,
|
child: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.max,
|
||||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && !cfg.drawerIsExpanded)
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Container(
|
children: [
|
||||||
decoration: BoxDecoration(
|
if (!kIsWeb &&
|
||||||
border: Border(
|
(Platform.isWindows ||
|
||||||
bottom: BorderSide(
|
Platform.isLinux ||
|
||||||
color: Theme.of(context).dividerColor,
|
Platform.isMacOS) &&
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
!cfg.drawerIsExpanded)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: WindowTitleBarBox(),
|
||||||
),
|
),
|
||||||
child: WindowTitleBarBox(),
|
Gap(MediaQuery.of(context).padding.top),
|
||||||
|
Expanded(
|
||||||
|
child: _DrawerContentList(),
|
||||||
),
|
),
|
||||||
Column(
|
if (cfg.hideBottomNav)
|
||||||
mainAxisSize: MainAxisSize.min,
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
spacing: 8,
|
||||||
children: [
|
children: nav.destinations.where((ele) => ele.isPinned).map(
|
||||||
Text('Solar Network').bold(),
|
(ele) {
|
||||||
AppVersionLabel(),
|
return Expanded(
|
||||||
],
|
child: Tooltip(
|
||||||
).padding(
|
message: ele.label.tr(),
|
||||||
horizontal: 32,
|
child: IconButton.filledTonal(
|
||||||
vertical: 12,
|
icon: ele.icon,
|
||||||
),
|
color: Theme.of(context)
|
||||||
...destinations.where((ele) => ele.isPinned).map((ele) {
|
.colorScheme
|
||||||
return NavigationDrawerDestination(
|
.onPrimaryContainer,
|
||||||
icon: ele.icon,
|
onPressed: () {
|
||||||
label: Text(ele.label).tr(),
|
GoRouter.of(context).goNamed(ele.screen);
|
||||||
);
|
Scaffold.of(context).closeDrawer();
|
||||||
}),
|
},
|
||||||
const Divider(),
|
),
|
||||||
...destinations.where((ele) => !ele.isPinned).map((ele) {
|
),
|
||||||
return NavigationDrawerDestination(
|
);
|
||||||
icon: ele.icon,
|
},
|
||||||
label: Text(ele.label).tr(),
|
).toList(),
|
||||||
);
|
).padding(horizontal: 16),
|
||||||
}),
|
Align(
|
||||||
],
|
alignment: Alignment.bottomCenter,
|
||||||
onDestinationSelected: (idx) {
|
child: ListTile(
|
||||||
nav.setIndex(idx);
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
GoRouter.of(context).goNamed(destinations[idx].screen);
|
leading: AccountImage(content: ua.user?.avatar),
|
||||||
Scaffold.of(context).closeDrawer();
|
title: Text(ua.user?.nick ?? 'unknown'.tr()).fontSize(15),
|
||||||
},
|
subtitle:
|
||||||
|
Text('@${ua.user?.name ?? 'unknown'.tr()}').fontSize(13),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.notifications, fill: 1),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('notification');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.settings, fill: 1),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('settings');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('account');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DrawerContentList extends StatelessWidget {
|
||||||
|
const _DrawerContentList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ct = context.read<ChatChannelProvider>();
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final nav = context.watch<NavigationProvider>();
|
||||||
|
final rel = context.read<SnRealmProvider>();
|
||||||
|
|
||||||
|
return PageTransitionSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
transitionBuilder: (Widget child, Animation<double> primaryAnimation,
|
||||||
|
Animation<double> secondaryAnimation) {
|
||||||
|
return SharedAxisTransition(
|
||||||
|
animation: primaryAnimation,
|
||||||
|
secondaryAnimation: secondaryAnimation,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
transitionType: SharedAxisTransitionType.horizontal,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: nav.focusedRealm == null
|
||||||
|
? ListView(
|
||||||
|
key: const Key('realm-list-view'),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Solar Network').bold(),
|
||||||
|
AppVersionLabel(),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
||||||
|
leading: const Icon(Symbols.home),
|
||||||
|
title: Text('screenHome').tr(),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).goNamed('home');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...rel.availableRealms.map((ele) {
|
||||||
|
return ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: AccountImage(
|
||||||
|
content: ele.avatar,
|
||||||
|
radius: 16,
|
||||||
|
),
|
||||||
|
title: Text(ele.name),
|
||||||
|
onTap: () {
|
||||||
|
nav.setFocusedRealm(ele);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: ListView(
|
||||||
|
key: ValueKey(nav.focusedRealm),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
if (nav.focusedRealm!.banner != null)
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
sn.getAttachmentUrl(
|
||||||
|
nav.focusedRealm!.banner!,
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
tileColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
contentPadding: EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 16,
|
||||||
|
),
|
||||||
|
leading: AccountImage(
|
||||||
|
content: nav.focusedRealm!.avatar,
|
||||||
|
radius: 16,
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
onPressed: () {
|
||||||
|
nav.setFocusedRealm(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(nav.focusedRealm!.name),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'realmDetail',
|
||||||
|
pathParameters: {
|
||||||
|
'alias': nav.focusedRealm!.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.only(
|
||||||
|
left: 28,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
leading: const Icon(Symbols.globe),
|
||||||
|
title: Text('community').tr(),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'realmCommunity',
|
||||||
|
pathParameters: {
|
||||||
|
'alias': nav.focusedRealm!.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (ct.availableChannels
|
||||||
|
.where((ele) => ele.realmId == nav.focusedRealm?.id)
|
||||||
|
.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
...(ct.availableChannels
|
||||||
|
.where((ele) => ele.realmId == nav.focusedRealm?.id)
|
||||||
|
.map((ele) {
|
||||||
|
return ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.only(
|
||||||
|
left: 28,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
leading: const Icon(Symbols.tag),
|
||||||
|
title: Text(ele.name),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'chatRoom',
|
||||||
|
pathParameters: {
|
||||||
|
'scope': ele.realm?.alias ?? 'global',
|
||||||
|
'alias': ele.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -107,6 +107,7 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.watch<ConfigProvider>();
|
final cfg = context.watch<ConfigProvider>();
|
||||||
|
final nav = context.watch<NavigationProvider>();
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
final isCollapseDrawer = cfg.drawerIsCollapsed;
|
final isCollapseDrawer = cfg.drawerIsCollapsed;
|
||||||
@ -118,8 +119,9 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
.last
|
.last
|
||||||
.route
|
.route
|
||||||
.name;
|
.name;
|
||||||
final isShowBottomNavigation =
|
final isShowBottomNavigation = cfg.hideBottomNav
|
||||||
NavigationProvider.kShowBottomNavScreen.contains(routeName)
|
? false
|
||||||
|
: nav.showBottomNavScreen.contains(routeName)
|
||||||
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
||||||
: false;
|
: false;
|
||||||
final isPopable = !NavigationProvider.kAllDestination
|
final isPopable = !NavigationProvider.kAllDestination
|
||||||
|
@ -151,6 +151,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postDetail',
|
'postDetail',
|
||||||
pathParameters: {'slug': _posts[idx].id.toString()},
|
pathParameters: {'slug': _posts[idx].id.toString()},
|
||||||
@ -225,6 +226,9 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
onPost: () {
|
onPost: () {
|
||||||
_childListKey.currentState!.refresh();
|
_childListKey.currentState!.refresh();
|
||||||
},
|
},
|
||||||
|
onExpand: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -103,7 +103,7 @@ class OpenablePostItem extends StatelessWidget {
|
|||||||
transitionType: ContainerTransitionType.fade,
|
transitionType: ContainerTransitionType.fade,
|
||||||
closedElevation: 0,
|
closedElevation: 0,
|
||||||
closedColor: Theme.of(context).colorScheme.surface.withOpacity(
|
closedColor: Theme.of(context).colorScheme.surface.withOpacity(
|
||||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0 : 1,
|
||||||
),
|
),
|
||||||
closedShape: const RoundedRectangleBorder(
|
closedShape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
@ -122,6 +122,7 @@ class PostItem extends StatefulWidget {
|
|||||||
final bool showMenu;
|
final bool showMenu;
|
||||||
final bool showFullPost;
|
final bool showFullPost;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
|
final bool showCompactAvatar;
|
||||||
final bool showExpandableComments;
|
final bool showExpandableComments;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final Function(SnPost data)? onChanged;
|
final Function(SnPost data)? onChanged;
|
||||||
@ -137,6 +138,7 @@ class PostItem extends StatefulWidget {
|
|||||||
this.showMenu = true,
|
this.showMenu = true,
|
||||||
this.showFullPost = false,
|
this.showFullPost = false,
|
||||||
this.showAvatar = true,
|
this.showAvatar = true,
|
||||||
|
this.showCompactAvatar = false,
|
||||||
this.showExpandableComments = false,
|
this.showExpandableComments = false,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
@ -297,185 +299,180 @@ class _PostItemState extends State<PostItem> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
constraints:
|
constraints: BoxConstraints(
|
||||||
BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
|
maxWidth: widget.maxWidth ?? double.infinity,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Row(
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (widget.showAvatar)
|
||||||
children: [
|
_PostAvatar(
|
||||||
Row(
|
data: widget.data,
|
||||||
|
isCompact: false,
|
||||||
|
),
|
||||||
|
if (widget.showAvatar) const Gap(12),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.showAvatar)
|
if (widget.showCompactAvatar)
|
||||||
_PostAvatar(
|
_PostAvatar(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
isCompact: false,
|
isCompact: true,
|
||||||
),
|
),
|
||||||
if (widget.showAvatar) const Gap(12),
|
if (widget.showAvatar) const Gap(8),
|
||||||
Expanded(
|
_PostContentHeader(
|
||||||
child: _PostContentHeader(
|
isRelativeDate: !widget.showFullPost,
|
||||||
isRelativeDate: !widget.showFullPost,
|
isCompact: false,
|
||||||
isCompact: false,
|
|
||||||
data: widget.data,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_PostActionPopup(
|
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
isAuthor: isAuthor,
|
|
||||||
onShare: () => _doShare(context),
|
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
|
||||||
onSelectAnswer: widget.onSelectAnswer,
|
|
||||||
onDeleted: () {
|
|
||||||
widget.onDeleted?.call();
|
|
||||||
},
|
|
||||||
onTranslate: () {
|
|
||||||
_translateText();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(8),
|
),
|
||||||
if (widget.data.preload?.thumbnail != null)
|
_PostActionPopup(
|
||||||
Container(
|
data: widget.data,
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
isAuthor: isAuthor,
|
||||||
decoration: BoxDecoration(
|
onShare: () => _doShare(context),
|
||||||
borderRadius: const BorderRadius.all(
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
Radius.circular(8),
|
onSelectAnswer: widget.onSelectAnswer,
|
||||||
),
|
onDeleted: () {
|
||||||
border: Border.all(
|
widget.onDeleted?.call();
|
||||||
color: Theme.of(context).dividerColor,
|
},
|
||||||
width: 1,
|
onTranslate: () {
|
||||||
),
|
_translateText();
|
||||||
),
|
},
|
||||||
child: AspectRatio(
|
),
|
||||||
aspectRatio: 16 / 9,
|
],
|
||||||
child: ClipRRect(
|
),
|
||||||
borderRadius: const BorderRadius.all(
|
const Gap(8),
|
||||||
Radius.circular(8),
|
if (widget.data.preload?.thumbnail != null)
|
||||||
),
|
Container(
|
||||||
child: AutoResizeUniversalImage(
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
sn.getAttachmentUrl(
|
decoration: BoxDecoration(
|
||||||
widget.data.preload!.thumbnail!.rid,
|
borderRadius: const BorderRadius.all(
|
||||||
),
|
Radius.circular(8),
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.data.preload?.video != null)
|
|
||||||
_PostVideoPlayer(data: widget.data).padding(bottom: 8),
|
|
||||||
if (widget.data.type == 'question')
|
|
||||||
_PostQuestionHint(data: widget.data).padding(bottom: 8),
|
|
||||||
if (_displayDescription.isNotEmpty ||
|
|
||||||
_displayTitle.isNotEmpty)
|
|
||||||
_PostHeadline(
|
|
||||||
title: _displayTitle,
|
|
||||||
description: _displayDescription,
|
|
||||||
data: widget.data,
|
|
||||||
isEnlarge: widget.data.type == 'article' &&
|
|
||||||
widget.showFullPost,
|
|
||||||
).padding(bottom: 8),
|
|
||||||
if (widget.data.type == 'article' && !widget.showFullPost)
|
|
||||||
Text('postArticle')
|
|
||||||
.tr()
|
|
||||||
.fontSize(13)
|
|
||||||
.opacity(0.75)
|
|
||||||
.padding(bottom: 8),
|
|
||||||
if ((_displayText.isNotEmpty) &&
|
|
||||||
(widget.showFullPost ||
|
|
||||||
widget.data.type != 'article'))
|
|
||||||
_PostContentBody(
|
|
||||||
text: _displayText,
|
|
||||||
data: widget.data,
|
|
||||||
isSelectable: widget.showFullPost,
|
|
||||||
isEnlarge: widget.data.type == 'article' &&
|
|
||||||
widget.showFullPost,
|
|
||||||
).padding(bottom: 6),
|
|
||||||
if (widget.data.visibility > 0)
|
|
||||||
_PostVisibilityHint(data: widget.data).padding(
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
if (widget.data.body['content_truncated'] == true)
|
|
||||||
_PostTruncatedHint(data: widget.data).padding(
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
if (widget.data.tags.isNotEmpty)
|
|
||||||
_PostTagsList(data: widget.data)
|
|
||||||
.padding(top: 4, bottom: 6),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
if (widget.showViews)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.play_circle, size: 20),
|
|
||||||
const Gap(4),
|
|
||||||
Text('postViews')
|
|
||||||
.plural(widget.data.totalViews),
|
|
||||||
],
|
|
||||||
).opacity(0.75),
|
|
||||||
if (_isTranslating)
|
|
||||||
AnimateWidgetExtensions(Row(
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.translate, size: 20),
|
|
||||||
const Gap(4),
|
|
||||||
Text('translating').tr(),
|
|
||||||
],
|
|
||||||
))
|
|
||||||
.animate(onPlay: (e) => e.repeat())
|
|
||||||
.fadeIn(duration: 500.ms, curve: Curves.easeOut)
|
|
||||||
.then()
|
|
||||||
.fadeOut(
|
|
||||||
duration: 500.ms,
|
|
||||||
delay: 1000.ms,
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
),
|
|
||||||
if (_isTranslated)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Symbols.translate, size: 20),
|
|
||||||
const Gap(4),
|
|
||||||
Text('translated').tr(),
|
|
||||||
],
|
|
||||||
).opacity(0.75),
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_displayText =
|
|
||||||
widget.data.body['content'] ?? '';
|
|
||||||
_displayTitle =
|
|
||||||
widget.data.body['title'] ?? '';
|
|
||||||
_displayDescription =
|
|
||||||
widget.data.body['description'] ?? '';
|
|
||||||
_isTranslated = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (widget.data.repostTo != null)
|
|
||||||
_PostQuoteContent(child: widget.data.repostTo!)
|
|
||||||
.padding(
|
|
||||||
top: 4,
|
|
||||||
bottom: widget.data.preload?.attachments
|
|
||||||
?.isNotEmpty ??
|
|
||||||
false
|
|
||||||
? 12
|
|
||||||
: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(
|
|
||||||
bottom:
|
|
||||||
widget.showViews || _isTranslated || _isTranslating
|
|
||||||
? 8
|
|
||||||
: 0,
|
|
||||||
),
|
),
|
||||||
],
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
sn.getAttachmentUrl(
|
||||||
|
widget.data.preload!.thumbnail!.rid,
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).padding(horizontal: 12, top: 8),
|
if (widget.data.preload?.video != null)
|
||||||
|
_PostVideoPlayer(data: widget.data).padding(bottom: 8),
|
||||||
|
if (widget.data.type == 'question')
|
||||||
|
_PostQuestionHint(data: widget.data).padding(bottom: 8),
|
||||||
|
if (_displayDescription.isNotEmpty || _displayTitle.isNotEmpty)
|
||||||
|
_PostHeadline(
|
||||||
|
title: _displayTitle,
|
||||||
|
description: _displayDescription,
|
||||||
|
data: widget.data,
|
||||||
|
isEnlarge:
|
||||||
|
widget.data.type == 'article' && widget.showFullPost,
|
||||||
|
).padding(bottom: 8),
|
||||||
|
if (widget.data.type == 'article' && !widget.showFullPost)
|
||||||
|
Text('postArticle')
|
||||||
|
.tr()
|
||||||
|
.fontSize(13)
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(bottom: 8),
|
||||||
|
if ((_displayText.isNotEmpty) &&
|
||||||
|
(widget.showFullPost || widget.data.type != 'article'))
|
||||||
|
_PostContentBody(
|
||||||
|
text: _displayText,
|
||||||
|
data: widget.data,
|
||||||
|
isSelectable: widget.showFullPost,
|
||||||
|
isEnlarge:
|
||||||
|
widget.data.type == 'article' && widget.showFullPost,
|
||||||
|
).padding(bottom: 6),
|
||||||
|
if (widget.data.visibility > 0)
|
||||||
|
_PostVisibilityHint(data: widget.data).padding(
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
if (widget.data.body['content_truncated'] == true)
|
||||||
|
_PostTruncatedHint(data: widget.data).padding(
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
if (widget.data.tags.isNotEmpty)
|
||||||
|
_PostTagsList(data: widget.data).padding(top: 4, bottom: 6),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
if (widget.showViews)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.play_circle, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
Text('postViews').plural(widget.data.totalViews),
|
||||||
|
],
|
||||||
|
).opacity(0.75),
|
||||||
|
if (_isTranslating)
|
||||||
|
AnimateWidgetExtensions(Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.translate, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
Text('translating').tr(),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
.animate(onPlay: (e) => e.repeat())
|
||||||
|
.fadeIn(duration: 500.ms, curve: Curves.easeOut)
|
||||||
|
.then()
|
||||||
|
.fadeOut(
|
||||||
|
duration: 500.ms,
|
||||||
|
delay: 1000.ms,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
),
|
||||||
|
if (_isTranslated)
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.translate, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
Text('translated').tr(),
|
||||||
|
],
|
||||||
|
).opacity(0.75),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_displayText = widget.data.body['content'] ?? '';
|
||||||
|
_displayTitle = widget.data.body['title'] ?? '';
|
||||||
|
_displayDescription =
|
||||||
|
widget.data.body['description'] ?? '';
|
||||||
|
_isTranslated = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (widget.data.repostTo != null)
|
||||||
|
_PostQuoteContent(child: widget.data.repostTo!).padding(
|
||||||
|
top: 4,
|
||||||
|
bottom: widget.data.preload?.attachments?.isNotEmpty ??
|
||||||
|
false
|
||||||
|
? 12
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
bottom: widget.showViews || _isTranslated || _isTranslating
|
||||||
|
? 8
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
).padding(horizontal: 12, top: 8),
|
||||||
),
|
),
|
||||||
if (displayableAttachments?.isNotEmpty ?? false)
|
if (displayableAttachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
@ -509,6 +506,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
_PostCommentIntent(
|
_PostCommentIntent(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
showAvatar: widget.showAvatar,
|
showAvatar: widget.showAvatar,
|
||||||
|
maxWidth: widget.maxWidth ?? double.infinity,
|
||||||
).padding(left: 12, right: 12)
|
).padding(left: 12, right: 12)
|
||||||
else
|
else
|
||||||
_PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth)
|
_PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth)
|
||||||
@ -558,10 +556,22 @@ class _PostItemState extends State<PostItem> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _PostContentHeader(
|
child: Row(
|
||||||
isRelativeDate: !widget.showFullPost,
|
children: [
|
||||||
isCompact: true,
|
if (widget.showCompactAvatar)
|
||||||
data: widget.data,
|
_PostAvatar(
|
||||||
|
data: widget.data,
|
||||||
|
isCompact: true,
|
||||||
|
),
|
||||||
|
if (widget.showCompactAvatar) const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: _PostContentHeader(
|
||||||
|
isRelativeDate: !widget.showFullPost,
|
||||||
|
isCompact: true,
|
||||||
|
data: widget.data,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_PostActionPopup(
|
_PostActionPopup(
|
||||||
@ -578,7 +588,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
).padding(bottom: widget.showCompactAvatar ? 4 : 0),
|
||||||
if (widget.data.preload?.thumbnail != null)
|
if (widget.data.preload?.thumbnail != null)
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
@ -755,19 +765,28 @@ class _PostItemState extends State<PostItem> {
|
|||||||
if (widget.showExpandableComments)
|
if (widget.showExpandableComments)
|
||||||
_PostCommentIntent(
|
_PostCommentIntent(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
|
maxWidth: (widget.maxWidth ?? double.infinity) -
|
||||||
|
(widget.showAvatar ? 72 : 24),
|
||||||
showAvatar: widget.showAvatar,
|
showAvatar: widget.showAvatar,
|
||||||
).padding(left: widget.showAvatar ? 60 : 12, right: 12)
|
).padding(left: widget.showAvatar ? 60 : 12, right: 12)
|
||||||
else if (widget.showComments)
|
else if (widget.showComments)
|
||||||
_PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth)
|
_PostFeaturedComment(data: widget.data, maxWidth: widget.maxWidth)
|
||||||
.padding(left: widget.showAvatar ? 60 : 12, right: 12),
|
.padding(left: widget.showAvatar ? 60 : 12, right: 12),
|
||||||
if (widget.showReactions)
|
if (widget.showReactions)
|
||||||
Padding(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
constraints: BoxConstraints(
|
||||||
child: _PostReactionList(
|
maxWidth: widget.maxWidth ?? double.infinity,
|
||||||
data: widget.data,
|
),
|
||||||
padding:
|
child: Padding(
|
||||||
EdgeInsets.only(left: widget.showAvatar ? 60 : 12, right: 12),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
onChanged: _onChanged,
|
child: _PostReactionList(
|
||||||
|
data: widget.data,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: widget.showAvatar ? 60 : 12,
|
||||||
|
right: 12,
|
||||||
|
),
|
||||||
|
onChanged: _onChanged,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -1552,19 +1571,24 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
if (isCompact) {
|
if (isCompact) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Text(data.publisher.nick).bold(),
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
data.publisher.nick,
|
||||||
|
maxLines: 1,
|
||||||
|
).bold(),
|
||||||
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Row(
|
Flexible(
|
||||||
children: [
|
child: Text(
|
||||||
Text(
|
isRelativeDate
|
||||||
isRelativeDate
|
? RelativeTime(context)
|
||||||
? RelativeTime(context)
|
.format((data.publishedAt ?? data.createdAt).toLocal())
|
||||||
.format((data.publishedAt ?? data.createdAt).toLocal())
|
: DateFormat('y/M/d HH:mm')
|
||||||
: DateFormat('y/M/d HH:mm')
|
.format((data.publishedAt ?? data.createdAt).toLocal()),
|
||||||
.format((data.publishedAt ?? data.createdAt).toLocal()),
|
maxLines: 1,
|
||||||
).fontSize(13),
|
overflow: TextOverflow.fade,
|
||||||
],
|
).fontSize(13).opacity(0.8),
|
||||||
).opacity(0.8),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -1583,7 +1607,10 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text('@${data.publisher.name}').fontSize(13),
|
Text(
|
||||||
|
'@${data.publisher.name}',
|
||||||
|
maxLines: 1,
|
||||||
|
).fontSize(13),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
isRelativeDate
|
isRelativeDate
|
||||||
@ -1591,6 +1618,8 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
.format((data.publishedAt ?? data.createdAt).toLocal())
|
.format((data.publishedAt ?? data.createdAt).toLocal())
|
||||||
: DateFormat('y/M/d HH:mm')
|
: DateFormat('y/M/d HH:mm')
|
||||||
.format((data.publishedAt ?? data.createdAt).toLocal()),
|
.format((data.publishedAt ?? data.createdAt).toLocal()),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
).fontSize(13),
|
).fontSize(13),
|
||||||
],
|
],
|
||||||
).opacity(0.8),
|
).opacity(0.8),
|
||||||
@ -1856,7 +1885,12 @@ class _PostTruncatedHint extends StatelessWidget {
|
|||||||
class _PostCommentIntent extends StatefulWidget {
|
class _PostCommentIntent extends StatefulWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
const _PostCommentIntent({required this.data, this.showAvatar = false});
|
final double maxWidth;
|
||||||
|
const _PostCommentIntent({
|
||||||
|
required this.data,
|
||||||
|
this.showAvatar = false,
|
||||||
|
required this.maxWidth,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_PostCommentIntent> createState() => _PostCommentIntentState();
|
State<_PostCommentIntent> createState() => _PostCommentIntentState();
|
||||||
@ -1895,54 +1929,69 @@ class _PostCommentIntentState extends State<_PostCommentIntent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Container(
|
||||||
children: [
|
constraints: BoxConstraints(maxWidth: widget.maxWidth),
|
||||||
if (_comments.isNotEmpty)
|
child: Column(
|
||||||
Card(
|
children: [
|
||||||
elevation: 4,
|
if (_comments.isNotEmpty)
|
||||||
margin: EdgeInsets.zero,
|
Card(
|
||||||
child: Column(
|
elevation: 4,
|
||||||
spacing: 8,
|
margin: EdgeInsets.zero,
|
||||||
children: [
|
child: Column(
|
||||||
for (final ele in _comments)
|
spacing: 8,
|
||||||
PostItem(
|
children: [
|
||||||
data: ele,
|
for (final ele in _comments)
|
||||||
showAvatar: false,
|
InkWell(
|
||||||
showExpandableComments: true,
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
showReactions: false,
|
child: PostItem(
|
||||||
showViews: false,
|
data: ele,
|
||||||
maxWidth: double.infinity,
|
showAvatar: false,
|
||||||
).padding(vertical: 8, left: 6),
|
showCompactAvatar: true,
|
||||||
],
|
showExpandableComments: true,
|
||||||
),
|
showReactions: false,
|
||||||
).padding(vertical: 8),
|
showViews: false,
|
||||||
Row(
|
maxWidth: double.infinity,
|
||||||
children: [
|
).padding(vertical: 8, left: 6),
|
||||||
Transform.flip(
|
onTap: () {
|
||||||
flipX: true,
|
GoRouter.of(context).pushNamed(
|
||||||
child: const Icon(Symbols.comment, size: 20),
|
'postDetail',
|
||||||
),
|
pathParameters: {'slug': ele.id.toString()},
|
||||||
const Gap(4),
|
extra: ele,
|
||||||
Text('postCommentsDetailed'.plural(widget.data.metric.replyCount)),
|
);
|
||||||
if (widget.data.metric.replyCount > 0 && !_isAllLoaded)
|
},
|
||||||
SizedBox(
|
),
|
||||||
width: 20,
|
],
|
||||||
height: 20,
|
),
|
||||||
child: IconButton(
|
).padding(vertical: 8),
|
||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
Row(
|
||||||
constraints: const BoxConstraints(),
|
children: [
|
||||||
padding: EdgeInsets.zero,
|
Transform.flip(
|
||||||
icon: const Icon(Symbols.expand_more, size: 18),
|
flipX: true,
|
||||||
onPressed: _isBusy
|
child: const Icon(Symbols.comment, size: 20),
|
||||||
? null
|
),
|
||||||
: () {
|
const Gap(4),
|
||||||
_fetchComments();
|
Text(
|
||||||
},
|
'postCommentsDetailed'.plural(widget.data.metric.replyCount)),
|
||||||
),
|
if (widget.data.metric.replyCount > 0 && !_isAllLoaded)
|
||||||
).padding(left: 8),
|
SizedBox(
|
||||||
],
|
width: 20,
|
||||||
).opacity(0.75).padding(horizontal: widget.showAvatar ? 4 : 0),
|
height: 20,
|
||||||
],
|
child: IconButton(
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(Symbols.expand_more, size: 18),
|
||||||
|
onPressed: _isBusy
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
_fetchComments();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).padding(left: 8),
|
||||||
|
],
|
||||||
|
).opacity(0.75).padding(horizontal: widget.showAvatar ? 4 : 0),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:ui';
|
|||||||
import 'package:croppy/croppy.dart';
|
import 'package:croppy/croppy.dart';
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:file_picker/file_picker.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';
|
||||||
@ -491,6 +492,14 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _selectFile() async {
|
||||||
|
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||||
|
if (result == null) return;
|
||||||
|
onAdd(
|
||||||
|
result.files.map((e) => PostWriteMedia.fromFile(e.xFile)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _pasteMedia() async {
|
void _pasteMedia() async {
|
||||||
final imageBytes = await Pasteboard.image;
|
final imageBytes = await Pasteboard.image;
|
||||||
if (imageBytes == null) return;
|
if (imageBytes == null) return;
|
||||||
@ -605,6 +614,18 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
_selectMedia();
|
_selectMedia();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.file_upload),
|
||||||
|
const Gap(16),
|
||||||
|
Text('addAttachmentFromFiles').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_selectFile();
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -9,6 +9,7 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.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';
|
||||||
|
import 'package:surface/screens/post/post_editor.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
@ -17,8 +18,10 @@ import 'package:surface/widgets/loading_indicator.dart';
|
|||||||
class PostMiniEditor extends StatefulWidget {
|
class PostMiniEditor extends StatefulWidget {
|
||||||
final int? postReplyId;
|
final int? postReplyId;
|
||||||
final Function? onPost;
|
final Function? onPost;
|
||||||
|
final Function? onExpand;
|
||||||
|
|
||||||
const PostMiniEditor({super.key, this.postReplyId, this.onPost});
|
const PostMiniEditor(
|
||||||
|
{super.key, this.postReplyId, this.onPost, this.onExpand});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostMiniEditor> createState() => _PostMiniEditorState();
|
State<PostMiniEditor> createState() => _PostMiniEditorState();
|
||||||
@ -214,12 +217,16 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postEditor',
|
'postEditor',
|
||||||
|
extra: PostEditorExtra(
|
||||||
|
text: _writeController.contentController.text,
|
||||||
|
),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
if (widget.postReplyId != null)
|
if (widget.postReplyId != null)
|
||||||
'replying': widget.postReplyId.toString(),
|
'replying': widget.postReplyId.toString(),
|
||||||
'mode': 'stories',
|
'mode': 'stories',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
widget.onExpand?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
|
@ -102,6 +102,6 @@ static void my_application_init(MyApplication* self) {}
|
|||||||
MyApplication* my_application_new() {
|
MyApplication* my_application_new() {
|
||||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
"application-id", APPLICATION_ID,
|
"application-id", APPLICATION_ID,
|
||||||
"flags", G_APPLICATION_NON_UNIQUE,
|
"flags", G_APPLICATION_DEFAULT_FLAGS,
|
||||||
nullptr));
|
nullptr));
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,8 @@ PODS:
|
|||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.49.1):
|
- sqlite3/fts5 (3.49.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
|
- sqlite3/math (3.49.1):
|
||||||
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.49.1):
|
- sqlite3/perf-threadsafe (3.49.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.49.1):
|
- sqlite3/rtree (3.49.1):
|
||||||
@ -200,6 +202,7 @@ PODS:
|
|||||||
- sqlite3 (~> 3.49.1)
|
- sqlite3 (~> 3.49.1)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- tray_manager (0.0.1):
|
- tray_manager (0.0.1):
|
||||||
@ -390,7 +393,7 @@ SPEC CHECKSUMS:
|
|||||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
|
||||||
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
|
sqlite3_flutter_libs: 487032b9008b28de37c72a3aa66849ef3745f3e6
|
||||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||||
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
48
pubspec.lock
48
pubspec.lock
@ -213,10 +213,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: chalkdart
|
name: chalkdart
|
||||||
sha256: e7cfcc9a9d9546843304c1ff87fe0696c7eb82ee70e6df63f555f321b15a40d8
|
sha256: "82dfa884e3cf97641eb0742a3b9ffd41490666b9ece548b2e32cbfefe540bf86"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.3"
|
version: "2.4.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -517,10 +517,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fast_rsa
|
name: fast_rsa
|
||||||
sha256: "205a36c0412b9fabebf3e18ccb5221d819cc28cfb3da988c0bf7b646368d0270"
|
sha256: a26ad752734dc52fd51abd55248df868d7480e68d8cc8dd12413b0124bba0a7e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.0"
|
version: "3.8.1"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -541,10 +541,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: ee11ce89f8937c39181bc88d57a455972f7545b86150d8f287d0d9cf95bcdf0a
|
sha256: "8d938fd5c11dc81bf1acd4f7f0486c683fe9e79a0b13419e27730f9ce4d8a25b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.2.1"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -746,10 +746,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_expandable_fab
|
name: flutter_expandable_fab
|
||||||
sha256: "85275279d19faf4fbe5639dc1f139b4555b150e079d056f085601a45688af12c"
|
sha256: b14caf78720a48f650e6e1a38d724e33b1f5348d646fa1c266570c31a7f87ef3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.4.0"
|
||||||
flutter_highlight:
|
flutter_highlight:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -953,10 +953,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: a6274c34d61b3d68082f2b0e9a641a3ec197e525d269f35b82f62d5b2c6d9f75
|
sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.4"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1177,10 +1177,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_linux
|
name: image_picker_linux
|
||||||
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1+2"
|
||||||
image_picker_macos:
|
image_picker_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1385,10 +1385,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: db745002d0323c32097f5bc23711c02b0fb36ef616011a38c8c1798d5684d368
|
sha256: "99d5b0e7c65232dfe1247e0ac67eeeee2cab9da2d860748fc495d34f5e9e6397"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2810.0"
|
version: "4.2811.0"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -2102,10 +2102,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
|
sha256: "1a96b59227828d9eb1463191d684b37a27d66ee5ed7597fcf42eee6452c88a14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.31"
|
version: "0.5.32"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2174,34 +2174,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: talker
|
name: talker
|
||||||
sha256: a074e0a2c1db1b09c1856050c5284a14ff64faea003403409871ab8335738d08
|
sha256: "45abef5b92f9b9bd42c3f20133ad4b20ab12e1da2aa206fc0a40ea874bed7c5d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "4.7.1"
|
||||||
talker_dio_logger:
|
talker_dio_logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: talker_dio_logger
|
name: talker_dio_logger
|
||||||
sha256: "6d713d26bdf90c7440222758a4e02ebfbdb3b323cdbdb50b78082db720f69427"
|
sha256: "52c1b554cccedec6073637a6d4f6a3e267dd4451c1545fe57e1b26897a560ccb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "4.7.1"
|
||||||
talker_flutter:
|
talker_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: talker_flutter
|
name: talker_flutter
|
||||||
sha256: "25a9faa98a0dcbab8d1ec366f19b3e91d422ede523461ec6e26e198b280cb98d"
|
sha256: "77458ca11638dfefb651e898a26101ee54e60dc0b168ad7481a05b1c97ce2680"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "4.7.1"
|
||||||
talker_logger:
|
talker_logger:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: talker_logger
|
name: talker_logger
|
||||||
sha256: "0a1a295a9d036a3416e89c7155eaaca9ca6e3e0abb5401a40597f98b9cfc8fe6"
|
sha256: ed9b20b8c09efff9f6b7c63fc6630ee2f84aa92661ae09e5ba04e77272bf2ad2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "4.7.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -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.4.2+81
|
version: 2.4.2+83
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart';
|
|||||||
import 'schema_v1.dart' as v1;
|
import 'schema_v1.dart' as v1;
|
||||||
import 'schema_v2.dart' as v2;
|
import 'schema_v2.dart' as v2;
|
||||||
import 'schema_v3.dart' as v3;
|
import 'schema_v3.dart' as v3;
|
||||||
|
import 'schema_v4.dart' as v4;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v2.DatabaseAtV2(db);
|
return v2.DatabaseAtV2(db);
|
||||||
case 3:
|
case 3:
|
||||||
return v3.DatabaseAtV3(db);
|
return v3.DatabaseAtV3(db);
|
||||||
|
case 4:
|
||||||
|
return v4.DatabaseAtV4(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3];
|
static const versions = const [1, 2, 3, 4];
|
||||||
}
|
}
|
||||||
|
2391
test/drift/my_database/generated/schema_v4.dart
Normal file
2391
test/drift/my_database/generated/schema_v4.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,8 +8,29 @@ auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
|
|||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
HANDLE g_hMutex = NULL;
|
||||||
|
|
||||||
|
bool CheckIfAlreadyRunning() {
|
||||||
|
g_hMutex = CreateMutex(NULL, FALSE, L"Global\\SolianDesktop");
|
||||||
|
|
||||||
|
if (g_hMutex == NULL) {
|
||||||
|
return true; // Mutex creation failed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||||
|
CloseHandle(g_hMutex);
|
||||||
|
return true; // Another instance is running
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||||
|
if (CheckIfAlreadyRunning()) {
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// Attach to console when present (e.g., 'flutter run') or create a
|
// Attach to console when present (e.g., 'flutter run') or create a
|
||||||
// new console when running with a debugger.
|
// new console when running with a debugger.
|
||||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||||
|
Reference in New Issue
Block a user